一个函数,为什么会有“颜色”?

Robert Nystrom 在 2015 年那篇经典博客《What Color is Your Function?》里,没有先讲事件循环、线程、Future、Promise,而是编了一个怪语言:函数只有两种,红色和蓝色。

蓝函数像同步函数,调用后直接拿值。红函数像异步函数,结果晚点回来,得靠 callback、Promise、Task/Future 之类的东西承载。麻烦不在颜色本身,而在传染:你调用了红函数,你的函数往往也得变红。

这就是那篇文章十年后还扎人的地方。它讲的不是某个语法糖顺不顺手,而是异步模型进入语言和核心库后,怎么长期改写 API、复用边界和程序员的脑内地图。

红蓝函数讲的是异步如何传染

Nystrom 的寓言很短,但刀口很准。

寓言规则现实含义直接后果
蓝函数同步函数调用后直接拿返回值
红函数异步函数结果稍后到来,要用 callback、Promise、Task/Future 包起来
红函数只能从红函数里自然调用异步结果不能在同步调用栈里直接变成普通值调用者被迫 async 化
核心库里有大量红函数I/O、网络、文件等 API 暴露异步差异业务层跟着染色

callback hell 不是程序员不会缩进,也不是代码品味差。它是平台把异步差异暴露给所有调用者后的自然结果。

Node 早期回调风格最痛的地方,是组合差、错误处理别扭、控制流断裂。你本来只想读个文件、查个接口、写段业务逻辑,结果代码形状先被异步模型拧了一遍。

Promise 是进步。它把异步操作变成一等对象,能链式组合,也更适合库封装。可它没有把红蓝世界合并。你拿到的仍不是值,而是一个“将来可能有值”的容器。

async/await 又往前走了一步。它让异步代码看起来更像同步代码,异常处理、循环、表达式都顺手许多。

但颜色还在。你要写 await,要返回 Promise、Task 或 Future,上层函数也常常要继续 async。语法糖把疼痛压下去了,没有把病灶切掉。

真正受影响的是 API,而不是几行代码

我不太买账的一种说法是:async/await 已经解决异步问题。

它解决的是书写体验里最疼的部分,不是抽象边界的问题。一个底层函数从同步改成异步,通常不是内部换个实现那么简单。调用它的封装函数、工具函数、测试代码、框架扩展点,都要重新表态。

受影响最具体的,是两类人。

写业务的开发者,会发现“加一个异步调用”很少只改一行。函数签名变了,测试写法变了,错误处理位置变了,调用方也要跟着改。

写库和框架的人更难。API 一旦公开,红蓝边界就变成契约。你今天返回一个普通值,明天改成 Promise,就不是小修小补,而是让使用者迁移调用方式。

现实里能做的不是消灭异步,而是控制染色半径:

场景更稳的做法代价
底层 I/O 必然异步把 async 边界固定在少数模块边界层代码会更厚
对外暴露库 API尽早决定同步版、异步版,别频繁改签名早期设计成本更高
测试异步逻辑明确使用 async 测试、mock Promise/Future测试工具链要跟上
业务函数复用避免让无关工具函数随便染红有时要多写适配层

这对团队选型也有现实含义。

如果项目大量依赖网络、数据库、队列和文件 I/O,选择显式 async 模型并不奇怪,甚至更诚实。成本摆在台面上,调度和等待都能被看见。

如果项目主要是 CPU 计算、规则处理、同步流程编排,那就要谨慎。别为了“现代”把一整条调用链染红。模型看着更先进,维护面可能更大。

“名不正,则言不顺。”函数看起来都叫函数,但一个返回值,一个返回未来值的包装。名字一样,抽象已经开始分叉。

接下来要看的是颜色边界,而不是关键字

有些语言靠线程和阻塞模型,让调用者暂时感觉不到颜色。比如传统 Java 代码里,很多调用看起来仍是普通函数调用。代价并没有消失,它藏在线程、调度、资源占用里。

另一些语言和生态选择让异步显形。JavaScript 是最典型的例子。它把等待写进 Promise、await 和调用链里,开发者很早就能看到成本,也很早就会被成本缠住。

这两条路没有纯粹的胜利者。

隐藏颜色,调用体验更统一,但底层资源账更容易被忽略。显式颜色,系统行为更可见,但 API 会被分成两套。并发不是免费午餐,只是账单寄给谁不同。

所以接下来最该观察的,不是某个语言又加了什么 async 语法,而是两个变量。

一个是核心库怎么设计边界。文件、网络、数据库、定时器这些基础能力,如果全部把颜色外溢给调用者,业务层就会长期背债。

另一个是框架怎么隔离颜色。好的框架会把异步集中在生命周期、请求处理、任务调度这些位置,不让每个小工具函数都被迫站队。差的框架会把红色一路泼到最上层,最后连一个简单校验函数都要想返回值模型。

这也是 Nystrom 那篇文章耐读的原因。它没有把 callback、Promise、async/await 判死刑。它只是提醒开发者:语法可以润色,调用链上的债不会自动消失。

Promise 不是废物,async/await 也不是骗局。它们都是止痛药。止痛药很好,但不能假装已经做完手术。

颜色真正要命的地方,不在多写几个字符,而在它改变了库的边界、复用的半径和团队迁移的成本。红蓝函数这根刺,扎的不是语法审美,是 API 设计的骨头。