作者讨论了在软件开发中修改全局API(全局变量)的危害性。文章以React、Next.js和Bun为例,这些框架在过去几个月中引入了基于修补全局变量的特性,引发了社区的大规模反对。作者强调,尽管这些框架的设计选择过去一直表现出色,但修补全局变量并不是一个好的API设计选择,无论其理由和吸引力如何。 文章详细讨论了修补全局变量的几个主要危害:

  1. 维护难度:修改不属于你的代码,维护起来非常困难。
  2. 可预测性:全局变量的行为如果被修改,会破坏代码的可预测性。
  3. 学习成本:忽视学习体验的软件工具注定会失败。一个好的API应该能够教育用户。
  4. 锁定效应:框架在标准API之上增加的“糖”越多,离开该框架就越困难。
  5. 阻碍进步:看似创新的实践最终可能成为进步的障碍。 作者还提出了替代方案,强调反对修补全局变量并不意味着反对便利性,而是应该更明确地表达。例如,通过显式导入特定的模块或函数来替代全局变量的修改。文章最后,作者反驳了一些常见的反对意见,并以感谢Lee Robinson和Jordan Harband校对文章并提供反馈结束。

老实说,我从未想过我会写关于这个问题,但修补全局变量的问题竟然是许多工程师似乎误解的话题之一。我们大多数人不做猴子补丁,甚至更少的人会在这些实现周围停留足够长的时间来见证它们的影响。更有理由谈论它。

在接下来的几千字中,我将把对全局 API 的修改称为“修补”。你拿到你不拥有的代码,修改它的行为,你就是在修补它。希望我们对语义有清晰的理解。

在本文中,我将使用 React、Next.js 和 Bun 作为示例,因为它们在过去几个月中引入了建立在修补全局变量基础上的功能,这在社区中引起了相当大的反对声。本文并非是对那种反对的一部分。如果你来这里是为了加强你对这些项目或背后公司的不喜欢,我建议你关闭这个标签页,继续你的方式。我公开表达了我对这个问题的担忧,并亲眼看到应得的批评被盲目的仇恨所混淆,我不再希望参与到那种讨论中。

我热爱 API 设计。所有这些公司过去都有出色的设计选择。今天,你将了解为什么修补全局变量从来都不是一个好选择,尽管它的理由和吸引力,我们也将讨论这一点。

Background 背景

当我听到 Next.js 中新的缓存 API 的公告时,我第一次遇到了“补丁问题”。虽然这个功能本身对开发人员和用户体验是一个很好的补充,但所提议的公共 API 却相当……不寻常。

团队决定通过扩展标准全局 API 来提供一个特定于 Next.js 的功能 — fetch

在进一步研究后,我很快发现 React 本身正在对全局 fetch 函数进行补丁,以在他们正在进行的 React 服务器组件模型中缓存服务器端响应。这是最常用的 JavaScript 框架之一,也是最常用的 JavaScript 元框架之一,引入了我认为是一种相当有害的设计模式。

几天前,我偶然发现了 Bun 的一条推文,提醒其用户有一种方便的代理 HTTP 请求的方法。只需给任何 fetch 请求添加一个 proxy 选项即可:

到这个时候,这已经是第三次一个主要框架(或者,在这种情况下,一个运行时)引入了一个依赖于修改现有全局 API 的功能。我当时(现在仍然)认为作者们是出于善意,但我忍不住担心这可能是一个疏忽,一个意外的火花,如果不加以扑灭,将笼罩我们整个珍爱的森林。

But why I think those APIs are bad? What is so harmful about them?
但是为什么我认为这些 API 不好呢?它们有什么害处?

在了解这些设计决策背后的原因之前回答这些问题将是不恰当的。

Appeal of patching globals

修补全局变量的吸引力

想出一个好的 API 并不容易。更何况,设计一个将被成千上万人使用的 API 更是如此。它必须直观、简洁,解决它旨在解决的问题,同时又要高效。有很多要考虑的因素,其中一些会让你的判断朝相反的方向发展。

幸运的是,今天我们所讨论的三个例子恰好涉及到 fetch 。这是一个在网络上进行 HTTP 请求的全局功能,被许多人所熟知和使用。当你想要发起一个请求时,你调用 fetch 。这难道不是一个完美的地方来表明你也希望该请求被缓存吗?也许在其他地方代理?而且因为它是全局的,甚至不需要导入!我们如此迅速地勾选这些选项,以至于我们在这里把铅笔磨成了短头。

到底,为什么有人要费力去发明定制功能和方法,而这些本来应该由平台自带的呢?

我确实理解这种吸引力。然而,每个决定都是有代价的,修补全局变量不仅会影响用户和维护者,还会积极损害原本很棒的 API 甚至语言本身。现在,具体来说。

危害 1:维护

当您打补丁时,您修改了您不拥有的东西。维护您不拥有的东西是困难的。这就是为什么您应该始终努力使您发布的体验明显和明确。就像这样:

这也将帮助您评估未来决定对框架进行的任何更改的爆炸半径。

看,全局变量就是那样——全局的。比认为你可以预测用户和其他工具在实际应用中使用全局变量的所有可能方式更糟糕的是,假设你可以为此做出调整。

嗨👋我以修补全局变量为生。我不喜欢那个。我实际上讨厌那个,如果我有选择的话,我会立刻远离那个(而我确实有选择,当它一闪而过时,将网络拦截移动到服务工作者)。不幸的是,我所做的工作性质并不给我太多选择。幸运的是,我今天提到的所有框架都有这种选择,并且我甚至会在本文后面展示一个替代设计来说明它们的 API。

但在我们讨论修补全局变量的危害之前不要这样做。

你可以相信我的话,也可以等待用户告诉你,如何干预全局变量会破坏他们的应用程序,以及他们为了发现这一点而遭受的极大痛苦。

你在源代码中设置了一个捕熊陷阱,有人中招了。至少,这不会让他们花费数小时的时间和理智来解决问题。但这会让你付出代价。

使用一个更具体的例子,想象一下,您的框架会对全局 fetch 函数进行补丁,以去重请求。这对 fetch 的直接消费者——您的用户来说效果非常好。现在,如果他们依赖于一个也内置了去重机制的第三方 SDK 会发生什么呢?这两种行为会发生冲突,导致各种奇怪的问题,几乎不可能调试。

这是你最理想的情况。最糟糕的情况是第三方也在修补 fetch ,并依赖该修补程序存在,而你的框架成功地通过引入自己的修补程序来撤销它。这是以最好的方式展现出的基于黑客精神的开发。

危害 2:可预测性

在工程领域中,几乎没有什么比了解事物的行为更令人满足的了。如果你使用 console.log() ,你知道它会将消息打印到控制台。现在,可能是浏览器的 DevTools 协议在做这件事,或者是 Node.js 写入 process.stdout ,但你知道会发生什么。你可以预测行为。

在工程中几乎没有比同一件事情表现不同更令人恼火的事情了。 的确,工程中几乎没有比同一件事情表现不同更令人恼火的事情了。

如果你已经在网络上建站很长时间了,我相信你一定有自己一套奇怪、古怪和行为怪异的记忆,你永远不愿意回顾。还记得以前 display: flex 只在某些浏览器中起作用吗?Safari 中的负回顾呢?还有那些花了无数时间恳求 IE 正确渲染页面的快乐时光?

当多个代理以不同方式实施相同事物时,就会发生混乱。

这让我更加珍惜现在。跨浏览器兼容性从未如此之高。即使跨环境兼容性也显示出令人难以置信的进步,Node.js 正在支持 fetchReadableStreamWebSocket 和其他十几个浏览器 API。这不是偶然发生的。作为一个行业,我们得出结论,没有人想在兼容性问题上浪费金钱和时间。我们投资于规范,淘汰旧的,为新的让路。

现在我们已经走了一整圈。我们又回到了多个代理以不同方式实现相同事物的阶段。

这是一个在 Next.js 中将被缓存并在 10 秒后重新验证的请求。

这个请求在 Svelte 中会表现得一样吗?在 Remix 中呢?在纯 JavaScript 中呢,也许?实际上并不会。重新验证的部分恰好是通过全局 fetch 泄漏出来的 Next.js 特定 API。这是一个伪装成全局变量的自定义 API,降低了其可预测性。

我故意没有举例说明 fetch 在不同运行时工作方式不同的标准行为。这些也存在,但也有努力弥合差距,提高规范合规性。另一方面,打补丁则完全朝着相反方向拉绳子。

危害 3:学习

忽视其创造的学习体验的软件注定会失败。

作为一名库的作者,我深知我所做决定对我的用户有多么重要。一个好的 API 必须完成很多事情:完成手头的任务,提供直观、多功能的手段,与已建立的理念和最佳实践相一致,可扩展、可维护和可转移。但还有一件事,它必须做的最重要的事情。

An API must educate. 一个 API 必须具有教育性。

一个好的工具会教会你概念。
一把糟糕的工具会教会你如何使用。

每当用户拿起您的工具时,他们都有问题要解决。而您提供的解决方案总是双管齐下:即时的和持久的。

由于没有更好的例子,我将无耻地使用一个来自我的经验。我花了去年的时间使 MSW 更符合规范。目标是促进标准的 Fetch API,以便开发人员更频繁地与其交互。这是我自己强加给自己的挑战和限制,我知道收益将是巨大的。

看,当开发人员来到 MSW 时,他们想要拦截请求并模拟响应。他们以前可以做到这一切,但 API 完全是刻意设计的,特定于该库:

这确实实现了目标。这确实拦截了请求并模拟了其响应。它有这个“即时”解决方案,随时可用。但是它根本未能教育,因此没有任何持久影响。

这个 API 是否教你如何处理网络请求?它是否让你探索响应是什么,如何构建响应?实际上并不是。它是否利用你已有的知识来提升你的体验?不是的。

Does it make you a better engineer?
这会让你成为更好的工程师吗?

无论如何,让我们回到正题。这个例子对我关于缓存有什么教训?

好吧,有时候,这些请求可以被缓存,并且有一个标准的 cache 属性来控制浏览器如何缓存这个请求。到目前为止一切顺利!但这是标准在教我,而不是 Next.js。

next.revalidate 部分完全是人为构造的。即使我在 Next.js 文档中查找,也不会知道它究竟是在抽象什么。它是控制缓存重新验证还是完全做其他事情?它肯定不会教我有关标准 Cache-Control HTTP 标头的 max-age 请求指令。

但更重要的是,我能否使用普通的 fetch 来编写相同的逻辑?

我真的不知道。这就是为什么我联系李寻求帮助。他友善地向我解释说 next.revalidate 专注于增量静态再生(ISR),而不仅仅是缓存。所以,不,我不能将其翻译成简单的 fetch 调用。看到它与 Next.js 紧密集成,我相信它甚至更不属于 RequestInit

尽管 next.revalidate 很强大,但我希望它能教我更多关于它究竟是如何运作的,这样我就能更好地理解缓存和 ISR。理想情况下,还能依赖现有的标准,这样我也可以在 Next.js 之外应用。

危害 4:锁定

框架在标准 API 之上撒的糖越多,就越难摆脱这个框架。

我不想成为一个工程师,不得不将这个从 Bun 迁移

因为我不知道 Bun 在这里做什么。我知道它应该从文档中做什么,但现实往往是这样,底层会发生许多事情。对于 proxy 的行为没有规范。现在我必须在迁移时匹配它的所有行为,因为每一个被忽略的怪异行为都可能导致我的应用程序中出现新的错误。
你拿了一样你不拥有的东西,基于方便起见对它进行了修改,现在它只能在你自己的小宇宙中运行。如果这不是供应商锁定的定义,那我不知道还有什么是。

Harm #5: Hurting the progress

伤害#5:阻碍进步

What appears like a pioneering of innovation ends up being one big spike in the wheel of progress. A really, really big spike.
看起来像是创新的先驱,最终成为进步之轮上的一个巨大刺。一个非常、非常巨大的刺。

Here’s a quick history lesson for you. In the hairy days of the Internet, there was a library called MooTools. It was a utility library to help developers survive the unfathomable chaos which was webdev some 20 years ago. Its goal was noble, its means, well, not so much.
这里有一个简短的历史课程。在互联网的早期时代,有一个名为 MooTools 的库。它是一个实用库,帮助开发人员在大约 20 年前的网络开发混乱中生存下来。它的目标是崇高的,但手段却不尽如人意。

See, MooTools worked by imbuing the prototypes of global JavaScript objects with extra functionality. In return, you got a ton of useful methods, like Array.prototype.getLast()Function.prototype.attempt(), and Number.prototype.limit(). None of those really existed in JavaScript but, in the end, it was in such a sorry state, to begin with. Nobody really cared.
看,MooTools 通过为全局 JavaScript 对象的原型添加额外功能来工作。作为回报,您获得了大量有用的方法,比如 Array.prototype.getLast()Function.prototype.attempt()Number.prototype.limit() 。这些在 JavaScript 中并不存在,但最终,它本来就处于如此糟糕的状态。没有人真的在乎。

Until more and more sites got built with MooTools. And when the time came to define the next chapter for ECMAScript, to address all those pains and make the language better, the committee was faced with a conundrum. They wanted to introduce new prototype methods on primitives but couldn’t because half of the web relied on the same methods from MooTools. Shipping those methods as a part of the language meant breaking half the Internet, which the committee found the wisdom not to do.
直到越来越多的网站使用 MooTools 构建。当确定 ECMAScript 的下一个章节,解决所有这些问题并改进语言的时候,委员会面临一个难题。他们想要在原始类型上引入新的原型方法,但无法这样做,因为一半的网络依赖于 MooTools 中的相同方法。将这些方法作为语言的一部分发布意味着破坏一半的互联网,委员会认为这样做是不明智的。

They had to bend the spec to reality. So we got String.prototype.includes() instead of String.prototype.contains() to stay consistent with Array.
他们不得不将规格调整到现实中。因此,我们得到了 String.prototype.includes() ,而不是 String.prototype.contains() ,以保持与 Array 的一致性。

Let me make this abundantly clear:
让我明确地表明:

The language itself couldn’t become more convenient because developers were addicted to a convenience library that modified things it didn’t own.
语言本身无法变得更加方便,因为开发人员沉迷于一个修改并非自己拥有的便利库。 沉迷于一个修改并非自己拥有的便利库的开发人员。

MooTools has also spawned a Smoosh controversy, causing a newly added Array.prototype.flatten to break the web and forcing the TC39 folks to rename a perfectly valid method name to flat.
MooTools 也引发了一个 Smoosh 争议,导致一个新添加的 Array.prototype.flatten 破坏了网络,迫使 TC39 的人们将一个完全有效的方法名称重命名为 flat

MooTools has passed into history, but its impact on JavaScript remained. So next time you choose to support short-sighted convenience, please do not complain if the WHATWG introduces a proxyTarget and proxyOptions properties to the fetch API instead of a single proxy.
MooTools 已经成为历史,但它对 JavaScript 的影响仍然存在。所以下次当你选择支持短视便利时,请不要抱怨如果 WHATWG 在 fetch API 中引入 proxyTargetproxyOptions 属性而不是单个 proxy

I admit, I don’t know the details on why MooTools chose prototype patching as their design direction. I will give them the benefit of the doubt, assuming they thought it the best way for their features to feel like the extension of the language instead of a vendor library. Perhaps they simply didn’t know better and there was no other example to learn from. I am thankful they have become one for the rest of us.
我承认,我不知道 MooTools 为什么选择原型修补作为他们的设计方向的细节。我会给他们一个机会,假设他们认为这是使他们的功能感觉像语言的延伸而不是供应商库的最佳方式。也许他们只是不知道更好的方法,也没有其他可以学习的例子。我很感激他们已经成为我们其他人的一个榜样。

A truly fascinating part here is that none of these costs and harms would even be relevant if one approached convenience with a bit more thought and precaution. It’s only proper we looked at alternatives to patching globals (I can’t believe I had to write this sentence).
这里真正迷人的部分是,如果一个人稍微多考虑一下和预防一下,这些成本和伤害甚至都不会有关。我们应该看看替代方案来解决全局变量的问题(我简直不敢相信我不得不写这句话)。

Alternatives 替代方案

There’s a widespread misbelief that convenience either comes directly from the spec or comes at the cost of violating one. I really don’t know where that stance is coming from so let me clarify this:
有一种普遍的错误观念,即方便要么直接来自规格,要么以违反规格为代价而获得。我真的不知道这种立场是从哪里来的,所以让我澄清一下:

Being against patching globals doesn’t mean being against convenience.
反对修补全局变量并不意味着反对方便。

I am all hands for convenience and yet I hate whenever frameworks patch globals.
我全力支持方便,但我讨厌框架在全局范围内打补丁。

But there is one thing I hate even more. Complaining without proposing solutions.
但有一件事我更讨厌。抱怨而不提出解决方案。

There are a ton of solutions to any API problem better than patching globals, and the quintessential part of any of them is being explicit. Abandon the magical, cast aside the pretense that you own fetch and it is only through modifying it that you can achieve that golden DX you dream about. That DX is fundamentally and irreversibly flawed, and I’ve just given you five extremely detailed reasons above as to why.
有很多比修补全局变量更好的解决方案来解决任何 API 问题,其中任何一个的基本部分都是明确的。放弃神奇,抛弃你拥有 fetch 并且只有通过修改它才能实现你梦寐以求的黄金 DX 的假装。那种 DX 从根本上和不可逆转地存在缺陷,我刚刚给出了五个极为详细的理由。

Let’s take Bun’s proxy API as an example and make it explicit:
让我们以 Bun 的 proxy API 为例,明确说明:

1import { proxy } from ‘bun’

2

3const proxiedRequest = proxy(proxyUrl, new Request(url, options))

4await fetch(proxiedRequest)

With this change, the proxying functionality no longer appears from a thin air. It has to be imported from bun explicitly. Nobody will confuse it with a standard JavaScript behavior. In fact, developers will be reminded on every import that it is Bun who’s making proxying easier. Here’s your developer-oriented marketing as a side effect of a good API design!
通过这种改变,代理功能不再像从空气中出现一样。它必须从 bun 中显式导入。没有人会将其与标准的 JavaScript 行为混淆。事实上,开发人员将在每次导入时被提醒,是 Bun 让代理变得更容易。这就是您作为一个好的 API 设计的副作用所带来的面向开发人员的营销!

But we can no longer shove it into fetch so we have to be smarter about our proxy function. We want to proxy a request, right? So, maybe, let’s accept any request instance as an argument and return a proxied request? It doesn’t interfere with how a Request is constructed or a fetch call is made all the while achieving the same proxying functionality? Get out of here.
但我们不能再把它塞进 fetch 里,所以我们必须更聪明地处理我们的 proxy 函数。我们想要代理一个请求,对吧?所以,也许,让我们接受任何请求实例作为参数,并返回一个代理请求?这不会干扰 Request 是如何构建的,或者 fetch 调用是如何进行的,同时实现相同的代理功能?滚开。

As a result of being an explicit utility, our proxy function doesn’t suffer any of the drawbacks inherent to patching globals. It’s a function owned by the framework, which makes it easy to version, maintain, modify, and deprecate. It’s predictable because you clearly see where it’s coming from and what it does. Most importantly, it doesn’t masquerade as a native JavaScript behavior. It’s extremely easy to learn: you go to the Bun’s documentation, see the function’s call signature, and … Wait, what is request: Request? What, I have to learn how to construct requests on the web now? Damn you, Bun, I didn’t ask you to make me smarter! There’s no lock-in by design, and if you wish to play chaotically good here, you can even publish proxy for other runtimes to use, boosting everybody’s awareness of your project and perhaps even incentivising it becoming a standard. And, finally, you start to inspire and incite progress instead of pretending it has happened already, the others just haven’t caught up yet.
由于是一个明确的实用程序,我们的 proxy 函数不会遭受补丁全局变量固有的任何缺点。它是框架拥有的函数,这使得它易于版本控制、维护、修改和弃用。它是可预测的,因为您清楚地看到它来自何处以及它的作用。最重要的是,它不会伪装成原生的 JavaScript 行为。它非常容易学习:您可以查看 Bun 的文档,看到函数的调用签名,然后…等等, request: Request 是什么?什么,现在我还得学习如何在网络上构建请求?该死,Bun,我没让你让我变聪明啊!设计上没有锁定,如果您希望在这里玩得混乱而有趣,甚至可以发布 proxy 供其他运行时使用,提高所有人对您项目的认识,甚至可能激励它成为一种标准。最后,您开始激发和鼓励进步,而不是假装进步已经发生了,其他人只是还没有赶上。

Do you see how a simple change made this API better? I’ll give it to you, it’s likely not production-ready because I had to spent the entirety of 10 seconds coming up with this example. But I bet with a team of smart people and a week or two of discussion, it would have become another small reason to switch over to Bun. Who knows, who knows.
你看到了吗,一个简单的改变如何让这个 API 变得更好?我会给你,这可能还不够成熟,因为我只花了整整 10 秒钟来想出这个例子。但我敢打赌,有一群聪明的人并经过一两周的讨论,它就会成为另一个转换到 Bun 的小理由。谁知道呢,谁知道呢。

Since the ball is already rolling, let’s refactor the APIs from React and Next.js as well. This is going to be mostly boring because we will apply the same pattern to all of them, ridding them of the same problems and reaping the same benefits.
既然事情已经开始了,让我们也一起重构 React 和 Next.js 的 API。这将会是相当无聊的,因为我们将会对它们都应用相同的模式,消除相同的问题并获得相同的好处。

1import { cache } from ‘react’

2

3// There’s no reason to couple the data fetching

4// function with React. By leaving it generic, it

5// can be reused and tested with fewer dependencies.

6function getUser() {

7 const response = await fetch(‘https://api.example.com/user’)

8 return response.json()

9}

10

11export async function UserProfile() {

12 // Instead, make caching (request deduplication)

13 // an explicit choice where we actually need it—

14 // when fetching the data for our component.

15 const profile = await cache(getUser())

16

17 return

Hello, {profile.name}!

18}

1import { withCache } from ‘next’

2

3// A regular request instance.

4const request = new Request(‘https://kettanaito.com’)

5

6// A regular fetch call that accepts a modified

7// request instance that instructs Next.js how to

8// cache this request.

9fetch(withCache(request, { revalidate: 10 }))

It speaks volumes that both React and Next.js are already in a process of migrating away from patching fetch by introducing their cache and unstable_cache APIs, respectivelly.
React 和 Next.js 都已经开始迁移,通过引入它们各自的 cacheunstable_cache API,逐渐摆脱对 fetch 的补丁,这说明了很多问题。

The suggestions I’m proposing are also less invasive, as they operate on existing globals and behaviors as opposed to modifying them.
我提出的建议也更少侵入性,因为它们是基于现有的全局变量和行为,而不是修改它们。

Common counter-arguments

常见的反驳论点

The amount of backlash toward the criticism of patching globals online made me realize most people lack the fundamental understanding of the matter. Below, I will address most common counter-arguments against my concerns just in case the article failed to convince you that meddling with globals is a terrible idea.
对在线修补全局变量的批评所引起的强烈反对让我意识到大多数人缺乏对这个问题的基本理解。接下来,我将针对最常见的反驳意见来回应我的担忧,以防文章未能说服您干预全局变量是一个糟糕的主意。

But who cares? 但谁在乎呢?

Not enough of us, sadly. Putting a jelly sandwich on a bus seat won’t affect the entire bus, but does it make it a good thing to do?
可惜我们的人数不够。在公交车座位上放一个果冻三明治不会影响整辆公交车,但这样做是正确的吗?

But it’s a better developer experience!

但这是更好的开发者体验!

If all the extensive explanations of this article failed to convinice you that patching globals is never a good developer experience, I fear nothing will.
如果这篇文章中所有详尽的解释都无法说服你,让你相信修补全局变量从来都不是一个好的开发者体验,那我恐怕没有什么能做到。

Bun is a custom runtime, there is no patching!

Bun 是一个自定义运行时,没有打补丁!

Bun is built on a promise of Node.js compatibility. Node.js implements fetch according to the WHATWG Fetch standard, and so Bun does too. The standard defines fetch in its entirety. With addition of custom properties, Bun is patching fetch, deviating from the standard and introducing all the issues I’ve talked about prior.
Bun 建立在与 Node.js 兼容的承诺之上。Node.js 根据 WHATWG Fetch 标准实现 fetch ,因此 Bun 也是如此。该标准完整定义了 fetch 。通过添加自定义属性,Bun 正在修补 fetch,偏离标准并引入了我之前谈到的所有问题。

But Node.js does weird stuff too!

但 Node.js 也会做一些奇怪的事情!

Of course it does! It’s a project with 15 years of history behind it. I swim in that weird stuff on a daily basis at work. We should learn from its oddities and lead by example, not use them as an excuse to implement harmful APIs.
当然了!这是一个拥有 15 年历史的项目。我每天在工作中都会接触到那些奇怪的东西。我们应该从它的怪异之处中学习,并以身作则,而不是借此为借口实施有害的 API。

But fetch doesn’t exist on the server!

但服务器上不存在 fetch !

Global fetch API exists in Node.js since 2022 (introduced as experimental in v17). Undici is the official implementer of that API and is a dependency to Node.js. People have been using it on the server for years now.
自 2022 年起(在 v17 中作为实验性功能引入),Node.js 中存在全局 fetch API。Undici 是该 API 的官方实现者,并且是 Node.js 的一个依赖项。人们多年来一直在服务器上使用它。

But these frameworks know better!

但是这些框架更懂!

They likely do! But in some cases, it takes them time and the feedback from the community to arrive at that “better”.
他们很可能会!但在某些情况下,他们需要时间和来自社区的反馈才能达到那种“更好”。

As I’ve mentioned before, React and Next.js are moving away from patching fetch in favor of more explicit APIs. When I asked Lee Robinson to provide more context behind that switch, he shared with me the following:
正如我之前提到的,React 和 Next.js 正在摒弃对 fetch 的修补,转而更倾向于更明确的 API。当我询问 Lee Robinson 关于这一转变背后更多的背景时,他与我分享了以下内容:

In theory, community packages could publish their own code that used fetch, and set these caching policies on their own. And we started to do that a bit, but it was very opaque and hard to tell why something was cached. So we decided we want to be more explicit. — Lee Robinson, VP of Product at Vercel.
从理论上讲,社区包可以发布自己使用 fetch 的代码,并自行设置这些缓存策略。我们开始做了一点,但是这非常不透明,很难说清楚为什么会缓存某些内容。因此,我们决定要更加明确。 — 李·罗宾逊,Vercel 的产品副总裁。

Perhaps we will even see Bun reconsider their direction of patching globals, which would immediately make it a more mature runtime, at least in my eyes.
也许我们甚至会看到 Bun 重新考虑他们修补全局变量的方向,这将立即使其在我看来更加成熟。

The bottom line is, there isn’t enough amount of experience, skill, or credibility that would render you immune to making a mistake. The only thing that matters is having the strength to admit one and the wisdom to resolve it.
底线是,没有足够的经验、技能或信誉可以使你免于犯错。唯一重要的是有勇气承认错误并有智慧解决它。


Conclusion 结论

I hope this article was able to shed some light on the dangers of apparently “controversial” practice of patching globals. It’s a footgun we have already fired, and seems we needed a due reminder that the feet we have are just two.
希望这篇文章能够揭示表面上“有争议”的全局变量修补实践的危险性。这是我们已经开火的脚枪,似乎我们需要提醒自己我们只有两只脚。

Huge thanks to Lee Robinson and Jordan Harband for proofreading this piece! Your feedback helped me ensure my example were accurate and my tone welcoming. I’m truly honored to have had your eyes on this one.