Promise、async/await 与生成器 — 单线程上等一个未来的全部机制
Promise、async/await 与生成器 — 单线程上等一个未来的全部机制
这一章讲三件事: promise——把一个「未来才会发生的结果」变成今天就能传递、 组合、安排后事的对象;async/await——把消费 promise 的链式写法,压回成看起来像同步的代码; 生成器——把函数切成「可以暂停、可以续跑」的片段,以及它和迭代器、异步流水线的关系。 读完你会明白:为什么 promise 的回调总比
setTimeout(..., 0)的回调先执行——这不是约定,是队列的硬规则。
1. 先看现象:同一个未来,三种写法
一个最简单的未来:「1 秒之后」。最原始的登记方式是把函数交给定时器:
setTimeout(() => { console.log("One second has elapsed"); }, 1_000);Promise 的写法把「那个未来」先造成一个对象:
const timerPromise = new Promise((resolve) => {
setTimeout(resolve, 1_000); // 1 秒后调用 resolve
});
timerPromise.then(() => { console.log("One second has elapsed"); });好像绕了一圈,但它把未来对象化了,立即换来三件回调做不到的事(原书列举):1
- 这个对象可以随便传——任何拿到它的代码都能给自己挂一个 then;
- then 可以挂任意多个——同一个 promise,多少回调排队都行;
- 可以写组合工具——比如「等这几个 promise 都落定之后」这种高层操作(§4)。
而 await 的写法把消费端再简化一层:
await timerPromise; // 代码在这「停」住……
console.log("One second has elapsed"); // ……promise 落定后,从下一行继续一眼看去这违反了「JavaScript 是单线程」的铁律——代码明明停了 1 秒!其实没有。这一章的旅程, 就是把「没有」两个字讲清楚。
主线例子:一次取数,三次网络故障
为了让每个机制都落在具体数字上,全章跟着一个 fetchJson(url) 走:
向 typicode 测试站取一篇帖子的 JSON。它一路上要闯三关:
① 网络本身通不通(断网 → fetch 的 promise 被拒)
② 服务器答没答好(状态码非 2xx → 我们主动 throw)
③ 正文是不是合法 JSON(response.json() 解析失败 → 被拒)三个关卡三种失败,却共享同一条错误通道——这就是 promise 的设计精髓。下面逐站走。
2. Promise 的地基:三态、落定,以及 resolve 的怪脾气
2.1 三态与一次性
一个 promise 任何时候必居三态之一:2
- pending(待定):结果还没来;
- fulfilled(已兑现):成功落定,带着值;
- rejected(已拒绝):失败落定,带着原因(reason,惯例是 Error 实例)。
fulfilled 或 rejected 合称 settled(已落定)。最硬的一条规则:
状态只能变一次。 一旦落定,状态和值永久冻结——再调多少次 resolve/reject 都是无效操作。3
2.2 executor:决议权只在内部
构造一个 promise 的形状:
const promise = new Promise((resolve, reject) => {
// 这坨代码叫 executor(执行器),创建 promise 的当场同步执行
// 通过 resolve / reject 两个参数掌握这个 promise 的命运
});注意方向性:promise 对象自己没有任何「改变状态」的方法——决议权完全封在 executor 内部。
改变状态只有三条路:调 resolve、调 reject、executor 里 throw(异常自动等价于 reject)。4
「没有对外改状态的方法」是个刻意的安全设计:状态的改变权只属于造它的人,消费方只能挂回调。 (调试工具能显示状态,那是读的引擎内部接口,不算对外方法。)
2.3 resolve 的怪脾气:遇到 thenable 会「跟着走」
resolve(普通值):promise 以该值兑现——这半句好懂。麻烦在另一半:5
如果
resolve收到的是一个带 then 方法的对象(术语 thenable), 这个 promise 不会立刻兑现,而是「订阅」那个 then,把对方的状态当作自己的。
promise 自己就有 then 方法,所以每个 promise 都是 thenable——推论:
const p1 = new Promise((resolve) => resolve(fulfilledPromise));
// p1 不直接兑现;它「跟随」fulfilledPromise,对方兑现什么值,它就是什么值
const p2 = new Promise((resolve) => resolve(rejectedPromise));
// p2 跟着 rejectedPromise 一起被拒带着数走一遍书里的例子:fulfilledPromise = Promise.resolve("fulfilled"),
p1 = new Promise(r => r(fulfilledPromise))——r 收到的是个 thenable,p1 订阅它,
fulfilledPromise 兑现 "fulfilled" 时,p1 也兑现 "fulfilled"。6
更极端的:thenable 的 then 如果永远不调任何回调,这个 promise 永远 pending——
resolve 了却没落定,这是新手最常栽的「promise 卡死」之一。
resolved ≠ settled(规范术语,原书特意分清):7
- resolved = resolve/reject 已经被调过(再调无效);
- settled = 状态进了 fulfilled 或 rejected。
一个 resolve 了 thenable 的 promise 是 resolved 但未 settled——它还在等别人。 日常口语里「resolved」常被当「fulfilled」用,规范里不是。
补充一个 ES2024 的方便货:Promise.withResolvers(),一次返回
{ promise, resolve, reject }——把决议函数掏出来带走的官方姿势,
以前要靠在 executor 里给外层变量赋值的 hack。8
3. then 的链式魔法:新 promise、错误传播、一个 catch 守三关
then(onFulfilled, onRejected) 的完整签名里藏了全章最重要的机制:
then 自己返回一个新的 promise。9
新 promise 的命运由回调的行为决定:
| 回调干了什么 | then 返回的新 promise |
|---|---|
| return 一个值 | 以该值兑现(走同一套 resolve 规则,return thenable 会跟随) |
| throw 一个错 | 以该错误被拒 |
| return 一个 promise | 变成那个 promise 的「拷贝」 |
错误沿链传播的机制由此浮出:任何一环的回调 throw,下一环的新 promise 就被拒,
再下一环还是新的拒绝……一路滚到链尾。而 catch(onRejected) 只是
then(undefined, onRejected) 的简写——所以一个 catch 能接住链上任何一环抛出的错。10
主线例子走完整链(书里的 fetch 流程,带三关):
fetch("https://jsonplaceholder.typicode.com/posts/1") // ① 关卡:网络
.then((response) => {
if (!response.ok) { // ② 关卡:状态码
throw new Error(`Status code ${response.status}`); // throw → 链进入拒绝
}
return response.json(); // ③ 关卡:解析;返回 promise → 链跟随
})
.then((json) => { console.log(json); }) // 拿到解析好的帖子
.catch((reason) => { console.error(`Request failed: ${reason}`); })
.finally(() => { console.log("Request finished"); }); // 落定必跑,fulfilled/rejected 都跑三关对号:断网 → fetch 的 promise 被拒 → 一路滚到 catch;
状态码 500 → 第二环 throw → 滚到 catch;正文非法 JSON → response.json() 的 promise 被拒 → 滚到 catch。
一个 catch,三种来源,全部兜住。 这是 promise 相对回调地狱的第一个质变:错误有了统一的通道。
finally(ES2018)补上了 try/catch/finally 的类比——它是「无论成败都要跑」的那一段
(清理工作),也是 then 的语法糖,不改动链上传递的值或错误。11
原书在这里记了两个工程教训:其一,promise 最大的坑是错误默认被静默吞掉——只挂 onFulfilled
忘了挂拒绝处理,拒绝就悄悄消失(typescript-eslint 的 no-floating-promises 规则专治此症);
其二,then/catch/finally 都改不了它们所挂的 promise——链上的每个环都是新对象,
能改状态的只有 resolve/reject。12
4. 多个 promise 的四种组合
并发的场景:渲染一个界面前要同时取三份数据。该同时发(不该串行等),
Promise.all 就是干这个的:13
Promise.all([p1, p2, p3].map(...)) // 输入 promise 数组
// 全部兑现 → 新 promise 兑现为「值数组」,顺序与输入一一对应
// 任何一个被拒 → 新 promise 立刻以该原因被拒「全好才算好、一坏全坏」——Promise.all 是典型的 all-or-nothing 工具,
也是实践中最常用的并发组合。顺序保证值得单独记:输出数组的第 i 项就是第 i 个输入的兑现值,
与谁先谁后完成无关。14
四个静态方法的取舍(原书给了完整对照的散件,我们合表):
| 方法 | 何时落定 | 失败语义 |
|---|---|---|
Promise.all | 全部兑现 | 任一拒绝 ⇒ 立即拒绝(全或无) |
Promise.allSettled(ES2020) | 全部落定(含拒绝) | 永不拒绝;结果数组带每项的 status 和 value/reason |
Promise.any(ES2021) | 第一个兑现 | 全部拒绝 ⇒ 以「原因数组」拒绝 |
Promise.race | 第一个落定(好坏都算) | 第一个落定的是拒绝 ⇒ 拒绝 |
race 的经典用法是给另一个 promise 掐表(书里的例子):15
const fetchController = new AbortController();
const fetchPromise = fetch("https://httpbin.org/delay/2", { signal: fetchController.signal })
.then((r) => { if (!r.ok) throw new Error(`Status code ${r.status}`); return r.json(); });
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => { fetchController.abort(); reject(new Error("Request timeout")); }, 1_000)
);
Promise.race([fetchPromise, timeoutPromise])
.then((result) => console.log(result))
.catch((reason) => console.error(`Request failed: ${reason}`));带数走:目标站 2 秒才响应,超时 1 秒先响——race 以 timeoutPromise 的拒绝落定,
catch 打出 "Request timeout"。注意 AbortController(ES2018):它是 promise 时代「取消」问题的官方答案,
signal 塞进 fetch 的选项,abort() 一调,底层请求真的被撤销——没有它,超时的请求还会在后台继续跑,
白白占着连接。原书称其为「被低估的特性」。16
any 的典型场景:「数据库和缓存同时查,缓存成了就行」;allSettled 的典型场景:
「全都要结果,失败的要失败详情」——书里还给了两者组合使用的思路(先 all 报总错、再 allSettled 补细节)。17
一个共同的天花板(原书边栏):这四个方法都只产出一个最终 promise,管不了 「有界并发」——比如 500 个任务只想同时跑 8 个。那要用 p-limit 这类库(Sindre Sorhus 家的一族: p-limit、p-throttle、p-debounce)。18
5. 微任务与宏任务:先讲纪律,再看 await 怎么服从它
5.1 两个队列,一条优先级
第 4 章说事件进队列;本章把「队列」这个词拆开——其实是两条:19
- 微任务队列(microtask queue),规范名 job queue:then/catch/finally 的回调进这里;
- 宏任务队列(macrotask queue),也叫 task queue:
setTimeout、DOM 事件、I/O 回调进这里。
调度规则只有三条,背下来:
1. 跑当前的代码,直到自然结束
2. 微任务队列非空 → 取一个微任务跑(反复直到清空)
3. 宏任务队列非空 → 取一个宏任务跑,然后回到 2微任务严格优先。 于是书上那个著名实验的输出毫无悬念:20
setTimeout(() => { console.log("setTimeout callback"); }, 0);
Promise.resolve(true).then(() => { console.log("then callback"); });
console.log("end of script"); end of script ← 同步代码先跑完(规则 1)
then callback ← 微任务清空(规则 2)
setTimeout callback ← 才轮到宏任务(规则 3)两个细节一起解释:其一,then 绝不同步调用回调——哪怕 promise 早已落定,
回调也要排到微任务里。这是 blessing:then 之后的代码永远先于回调执行,控制流可预测。21
其二,setTimeout(..., 0) 名不副实:HTML Living Standard 规定它的最小延迟是 4 毫秒;
但哪怕真是 0,then 也赢——赢在队列,不赢在时间。22
分属两份规范的纪律(书里指出的精微处):promise 与微任务的规则在 ECMAScript 规范里
(引擎必须遵守);setTimeout/queueMicrotask 的定时规则在 HTML Living Standard 里
(非浏览器运行时本来可以不同——Node 开了「跟随 HTML 规范」的先例,大家都跟了)。23
queueMicrotask 是把代码排进微任务的官方 API(以前是「resolved 的 promise 的 then」的歪招);
React 批量更新 DOM 这类「要比 setTimeout 更快的异步」用的就是它。24
5.2 await 的真身:挂起 + 微任务续跑
语法上 await 值 做两件事:把值按 resolve 的同一套规则转成 promise(等价 Promise.resolve(值)),
然后把当前函数挂起,直到 promise 落定。25
「挂起」不是阻塞——挂起的瞬间,函数把控制权交还事件循环,其他事件照常处理(这正是 §1 谜底的一半)。 promise 一落定,恢复的方式就是排一个微任务:
- 落定为 fulfilled:恢复执行,
await表达式取兑现值; - 落定为 rejected:在 await 那一行抛出拒绝原因。
三个微型的行为样例(原书的):
console.assert((await "Phantom Blade") === "Phantom Blade"); // 非 promise 值:原样返回
console.assert((await Promise.resolve("Falling Devil")) === "Falling Devil");
await Promise.reject(new Error("Deadly Mirage")); // 在 await 行 throw「在 await 行 throw」这一条是 await 相对 then 的隐藏升级:拒绝变成了带完整调用栈的普通异常, try/catch 直接接——promise 时代早期最大的抱怨(错误没有栈、不知哪里抛的)就此了结。26
那「为什么允许 await 的地方这么少」也有了答案:await 只允许出现在两个位置——
ECMAScript 模块的顶层、用 async 声明的函数体内。原因正是 §1 的另一半谜底:
如果任何函数都能 await,就意味着任何函数都可能「在半中间暂停、把控制权交出去」——
调用方「函数体一鼓作气跑完」的古老假设将全军覆没。async 函数的保证
(总返回 promise)把「暂停」关进了 promise 的笼子里:调用方看到的只是一次普通的异步返回。27
.mts 后缀(第 3 章见过)在这里派上用场:它是 ESM,所以模块顶层随便 await。28
5.3 async:保证「产出 promise」
async 只出现在函数定义处(function 或箭头函数前),效果两条:29
- return 的值按 resolve 规则包成 promise(已是 promise 则原样);
- throw 的值变成拒绝的 promise(
throw error≡return Promise.reject(error))。
于是「调用 async 函数永远拿到 promise」成为类型层的保证:
TypeScript 里 makeNinjaName 的返回类型显示为 Promise<string>。
(拒绝原因的类型总是 unknown——和 catch 到的错误一样,这是检查器承认「拒绝可以抛任何东西」。)
主线例子的最终形态,两个关键字合体:
const fetchJson = async (url: string) => {
const response = await fetch(url); // 挂起,网络回来续跑
if (!response.ok) { // 状态码关卡
throw new Error(`Status code ${response.status}`);
}
return response.json(); // 返回 promise:async 会跟随
};
try {
console.log(await fetchJson("jsonplaceholder.typicode.com/posts/1"));
} catch (error) { // 三个关卡的所有错误,一个 catch
console.error(`Request failed: ${error}`);
} finally {
console.log("Request finished");
}对比 §3 的链式版本:逻辑顺序=书写顺序,错误处理=try/catch/finally——异步代码终于有了
同步代码的全部 ergonomics(人机工学)。书里记了一句时代观察:API 设计因此整体从
「接受回调」转向「返回 promise」——Node 的 fs 模块老接口之外有了 fs/promises,
还有 util.promisify 把任何老式回调函数包成返回 promise 的函数。30
一条 lint 纪律:promise-function-async 规则建议「凡返回 promise 的函数都标 async」,
动机是错误处理的一致性——调用方期望「错误从 promise 里来」,而不是函数同步 throw。31
6. 生成器:把函数切成可恢复的片段
6.1 来龙去脉:期望落空的小历史
生成器和 promise 同在 ES2015 登场,当年被期待组成「消灭回调地狱的双人组」。
结果没成:语法怪(function*),更关键的是 ES2017 的 async/await 拿走了它的主战场。
今天生成器被视为「小众但有用」的工具——这是原书给的诚实定位。32
理解它的钥匙是上一句话的反面:async 是「定义返回 promise 的函数的语法糖」, 生成器是「定义返回迭代器的函数的语法糖」。 所以要先讲迭代器。
6.2 迭代器协议:for...of 底下的那套约定
ES2015 的 for...of 不只服务数组,它能遍历任何满足两条约定的对象:33
- 对象上有一个键叫
Symbol.iterator(Symbol 是 ES2015 引入的「唯一非字符串的键类型」,Symbol.iterator是 Symbol 类上的预置静态属性之一); - 那个键对应的值是个函数,调用它返回一个带
next()方法的对象;每次调next(), 要返回一个{ value, done }形状的对象。
满足这两条的对象叫 iterable(可迭代对象);next() 所在的对象叫 iterator(迭代器)。
for...of 做的事,本质就是反复调 next() 直到 done 为 true。
书里把数组的迭代器掏出来手动走了一遍(「非常啰嗦,但展示了 for...of 底下没什么魔法」):34
const numbers = [1, 2, 3];
const iterator = numbers[Symbol.iterator]();
let r = iterator.next(); // { value: 1, done: false }
while (!r.done) { console.log(r.value); r = iterator.next(); }
// 1 2 3,最后 r = { value: undefined, done: true } 循环结束迭代协议还藏在两个熟面孔底下:解构也是迭代——const [a, b, c] = ["a","b","c"] 其实是
「调迭代器取前三个值」;展开/剩余也是迭代——func(...iterable)、[...iterable]、
const [first, ...others] = iterable。35
一个自造 iterable 的秀肌肉例子(书里的):无限随机数流——36
const randomNumbers: Iterable<number> = {
[Symbol.iterator]: () => ({
next: () => ({ value: Math.random() }), // 永远没有 done: true
}),
};
const [num1, num2] = randomNumbers; // 解构取前两个,优雅停下
console.log(num1, num2);迭代器可以是无限的、可以惰性产生值(要一个产一个)——这是数组给不了的灵活性, 也是生成器登场的理由。
6.3 生成器:yield 是「暂停并吐出一个值」
生成器函数的定义: function 后加 *(箭头函数不行)。它一被调用,不执行函数体,
而是返回一个生成器对象——这个对象既是 iterable 又是 iterator(调它自己的
[Symbol.iterator]() 返回它自己)。37
function* randomNumbers() { // 生成器函数
while (true) { yield Math.random(); } // yield:暂停在这一行,把值递出去
}
const g = randomNumbers(); // 函数体一行没跑,拿到生成器对象
const [num1, num2] = g; // 迭代它:两次 next(),取两个随机数next() 和 yield 的精确协议(全章最该背的一段):38
next()一调,函数体从上次停的地方(或开头)跑到下一个 yield; yield 后表达式的值成为本次的{ value, done: false };- 函数return(或自然走到末尾):
{ value, done: true }; - 函数 throw:异常从
next()调用处抛出。
「跑到 yield 就暂停、保持全部局部状态」是它和普通函数的本质区别——await 暂停整个线程
(让事件循环走),yield 只暂停自己(控制权立刻回到调 next() 的地方,就像 return 一样),
下次 next() 从断点续跑。39
双向通道(较少用但书里给了完整例子):next(值) 可以把值塞回生成器——
恢复执行时,那个 yield 表达式的值就是你塞进去的值:40
function* multiplier(): Generator<number, unknown, number> {
let number = 1;
while (true) {
const multiplier = yield number; // 先吐出 number,再把 next(传入值)交给 multiplier
number *= multiplier;
}
}
const g = multiplier();
g.next().value === 1; // 吐出 1
g.next(5).value === 5; // 1×5=5
g.next(4).value === 20; // 5×4=20yield* 把迭代委托给另一个生成器(书里的武士名单例子):41
function* warriors() {
yield "Sun Tzu";
yield* ninjas(); // 先把 ninjas 的全部 yield 走完
yield "Genghis Khan";
}
function* ninjas() { yield "Hattori"; yield "Yoshi"; }
[...warriors()]; // ["Sun Tzu", "Hattori", "Yoshi", "Genghis Khan"]yield* 的杀手场景是递归:一个生成器可以把「同一生成器函数的另一份调用」委托下去——
于是「遍历一棵 DOM 树」可以写成一个朴素的 for...of,而不必先摊平成数组。42
6.4 异步生成器:for await...of 与流
普通生成器的 yield 不会暂停线程——这对「迭代下一个值」是对的语义,但遇上异步数据源就不够了。
ES2018 的答案:async 生成器(async function*),配 for await...of 消费。43
协议只换一处:异步迭代器的 next() 返回 promise(落定值为 { value, done }),
键从 Symbol.iterator 换成 Symbol.asyncIterator。但写代码时这些都被语法藏起来了:
async function* fetchPosts() {
for (let i = 1; i <= 3; i++) {
const response = await fetch(`jsonplaceholder.typicode.com/posts/${i}`);
if (!response.ok) throw new Error(`Status code ${response.status}`);
yield await response.json(); // await 和 yield 终于合体:这才是「真暂停」
}
}
for await (const post of fetchPosts()) { console.log(post); }书里的点睛句:「结合了 yield 与 async 和 await,我们终于有了一种在生成器函数内部真正暂停代码执行的能力。」 每循环一次:取第 i 篇 → 等网络 → 解析 → 吐出——三件事按书写顺序发生,全部是非阻塞的等待。44
消费端的不对称要记:异步迭代器没有解构、不能展开传参,唯一的语法就是 for await...of
(以及直接调 next())。45
现实坐标:流式 API(ReadableStream)也是异步可迭代对象——「和网页版大模型对话时, 文字一段段往外冒」很可能就是一个 for await...of 在渲染流(chunk)。原书把这条线指到了第 11 章(streams,MEAP 尚未出)。46
什么时候该写生成器(原书的收尾判断):当你想把「产生值的过程」包成 iterable 让别人好消费时—— 解构赋值、递归遍历树的 for...of、逐个处理昂贵任务的 for await...of,都是它的甜区。 「下次写着复杂循环时,问自己:写成生成器会不会更优雅。」47
7. 作者的判断与证据
可亲手验证的机制:
- 三态/一次性/thenable 跟随,全部有 assert 级示例(如
promise1/promise2的输出文本); - 微任务实验的输出顺序(end of script → then callback → setTimeout callback)在任何环境稳定复现;
multiplier生成器的 1、5、20 三个断言是逐值可验的。48
作者立场(分开读):
- 「生成器期望落空、如今小众」是行业观察,但「yield 不适合暂停线程」的机制论证是硬事实;
- 「AbortController 被低估」是作者的评价,它确实是取消问题的标准答案;
- 「then 回调永不同步」被称 blessing——对可预测性是 blessing,对「以为同步」的新手是坑的两面。
8. 边界与局限
- thenable 的识别靠「有没有 then 方法」,鸭子类型的双刃剑:普通对象手滑加个 then 属性, resolve 它就会被当成 thenable。书里没展开这个陷阱,但机制解释足够推出它。
- Promise 组合方法不管有界并发——p-limit 这类库不在书内(只给了名字和链接)。
- async 生成器错误处理(生成器内部 throw 在 for await 里的表现)书中未细讲。
- MEAP 缺口: streams(第 11 章)与模块(第 13 章)都被引用但不存在; 取消(cancellation)的完整讨论要等后续版本。
9. 可带走的(合上这一章,你应该有这十二条)
- promise 是「一次未来」的对象化:能传、能挂多个 then、能组合——回调给不了的三件事。
- 三态 + 落定后永久冻结;状态改变权封在 executor 里,消费方只有 then/catch/finally。
- resolve(thenable) 会跟随对方——所以 resolved ≠ settled,卡死的 promise 多半是这么来的。
- then 永远返回新 promise:回调 return 值→兑现、throw→拒绝、return promise→跟随——链式与错误传播都由此而来。
- 一个 catch 守多关:链上任何一环的拒绝/throw 都滚到链尾,这是 promise 对回调的第一个质变。
- all(全好才算好)、allSettled(全都要结果)、any(谁先成算谁)、race(谁先落定算谁);race+AbortController 是掐表的标准姿势。
- 微任务严格优先于宏任务——then 总比 setTimeout 先跑;then 绝不同步调回调。
- await = 转成 promise + 挂起函数 + 落定后排队恢复(fulfilled→取值;rejected→在 await 行 throw,带完整栈)。
- await 只许在两个地方:模块顶层、async 函数体内——这是「函数可能中途让权」的隔离带。
- async 保证函数总返回 promise:return 包一层、throw 变拒绝;
Promise<string>是类型层的承诺。 - 生成器 = 返回迭代器的语法糖:next() 跑到下个 yield 就暂停并保持状态;yield* 委托,天然适合递归遍历。
- async 生成器 + for await...of 是唯一「真暂停」的迭代——流式数据(大模型打字机效果)的标准消费姿势。
11. 深读一:决议算法的完整规则书
第 2 节把 resolve 的三条路径和 thenable 讲了,这里把「一个 promise 的一生」 整理成一份可查阅的规则书——每一条都对应书里的一个断言或可观察行为。
创建:new Promise(executor)
└─ executor 同步执行;它手里握着这个 promise 的全部命运
落定(三选一,先发生者赢,之后的一切调用无效):
① resolve(普通值) → fulfilled,值 = 该值
② reject(原因) → rejected,原因 = 该值(惯例是 Error 实例)
③ executor 里 throw → rejected,原因 = 被抛出的值
resolve(thenable) 的跟随规则:
└─ 订阅 thenable 的 then;对方兑现什么/因何拒绝,本 promise 照抄
└─ 对方永远 pending,本 promise 也永远 pending(已 resolved 但未 settled)
消费:then(onFulfilled, onRejected)
└─ 回调永不同步执行,一律进微任务队列
└─ then 返回【新】promise:回调 return 值→新 promise 兑现(同样走跟随规则)
回调 throw → 新 promise 拒绝三条最容易栽的规则,各配一个书里的实证:1
// 实证①:resolve 不保证落定
new Promise((resolve) => resolve({ then: () => {} })); // 永远 pending
// 实证②:resolve(另一个 promise) = 跟随
const p1 = new Promise((r) => r(Promise.resolve("fulfilled")));
// p1 不直接兑现;等内层兑现 "fulfilled",p1 才兑现同一个值
// 实证③:thenable 判定看「有没有 then 方法」
// ——所以普通对象手滑加了个 then 属性,resolve 它就会把它当 thenable(跟随的陷阱)「决议函数可以逃出 executor」这个老 hack 也值得一记(书里给了):
在 executor 里把 resolve/reject 赋给外层变量,别处就能调用——
ES2024 的 Promise.withResolvers() 就是把这个 hack 收编为正式 API:
const { promise, resolve, reject } = Promise.withResolvers();。
它是「回调式 API 与 promise 的桥」:把 promise 的 resolve 当回调传给老式接口,
一次调用就把那个老接口 future 化了。2
12. 深读二:错误处理的工程面
12.1 一个 catch 管三关的完整账
第 3 节的 fetch 链,把错误通道的「汇总能力」拆开记账:
fetch(url) ──可能断网──────► 它的 promise 被拒 ──┐
.then(检查 status) ──主动 throw 错误 ────────┤ 三条支流
.then(解析 JSON) ──正文不合法───────────────┘
│
▼ 都汇入同一个拒绝通道
.catch(reason) ──一次处理这个设计在回调时代不可想象:嵌套回调里,每个分支都要自己传错误、自己决定要不要继续传。 promise 把「错误的流向」从每个回调的私事,变成链的公共设施—— catch 之于 promise 链,正如 catch 之于 try/catch(方法名都不是巧合)。 finally(ES2018)补的是第三块拼图:无论成败都要执行的收尾(关 loading、记日志), 它不接收值也不改链上的值。3
12.2 静默失败:被低估的头号坑
书里对 promise 的抱怨排了名,第一名叫「错误默认被静默忽略」:
fetchJson("/api/data").then((data) => render(data));
// 若 promise 被拒绝,且链上没有 onRejected——拒绝悄悄消失,控制台可能只有一行系统警告对策是 lint 层的:typescript-eslint 的 no-floating-promises 规则强制要求
每个 promise 的拒绝都被显式处理(或显式声明「我不处理」)。
一条规则补上语言设计里没做的那半件事。4
12.3 组合四方法的选型决策树
多个 promise,我想要……
│
├─ 全部成功才算成功,一个坏全完 → Promise.all
├─ 每个的结果(成败都要)都拿到 → Promise.allSettled
├─ 谁先成功算谁赢(备胎模式) → Promise.any
└─ 谁先有结果算谁(含失败)/掐超时 → Promise.raceall 的顺序保证值得单独再记一次:输出数组的第 i 项 = 输入第 i 个 promise 的兑现值, 与完成先后无关——所以你可以放心地把「请求列表」和「结果列表」按下标对起来。5
race 掐超时的完整剧本(书里的 Listing 8.5)再压成一张时刻表:
0ms 发请求(httpbin 的 /delay/2,2 秒后才响应);同时起 1 秒超时的定时器
1000ms 超时先到:timeoutPromise 拒绝 → race 落定(拒绝) → catch 打出 "Request timeout"
且 AbortController.abort() 撤销了底层请求——不占连接
2000ms 服务器就算后来响应了,也没人听(fetch 已被撤销)AbortController 的价值在第二行:没有它,「超时」只是「不等了」, 请求还在后台烧资源;有了它,超时是「真的取消」。书里的评语:「underused feature」。6
12.4 有界并发:组合方法够不着的那块
all/any/race/allSettled 有个共同的天花板(书里立框指出):它们只返回一个 promise, 管的是「最终事件」或「首个事件」,管不了「同时最多跑几个」。 场景:500 个任务想限制到同时 8 个(比如开 Web Worker 得省着内存)。 答案是库:Sindre Sorhus 的 p-limit(配任何返回 promise 的函数用), 它所属的 p-* 轻量库家族还有 p-throttle(限流)、p-debounce(防抖)。7
13. 深读三:微任务纪律的完整版
13.1 两条队列的「优先级」究竟是什么意思
「微任务严格优先」要读成一条循环纪律,而不是「微任务先排队」:
跑当前代码(直到自然结束)
↓
清空微任务队列(全部跑完,新来的微任务也立刻跑)
↓
取一个宏任务执行
↓
回到「清空微任务队列」「清空」两个字是关键:一个微任务里如果又排了微任务(then 回调里再 .then),
它也在本轮执行——宏任务得等整列微任务排空。这就是为什么
Promise.resolve().then(() => Promise.resolve().then(() => console.log("深层")))
的每一层都跑在任何 setTimeout 之前。8
实证(书里的 Listing 8.6)再贴一次,它值得背下来:
setTimeout(() => { console.log("setTimeout callback"); }, 0);
Promise.resolve(true).then(() => { console.log("then callback"); });
console.log("end of script");
// end of script → then callback → setTimeout callback三个层次一次看全:同步代码最先(规则一);promise 已落定,then 的回调仍要排队(规则二—— 「promises never call the then callback synchronously」是刻意的,它让 then 之后的代码 永远先于回调执行,控制流可预测);setTimeout(...,0) 名不副实——HTML 标准规定它最小延迟 4 毫秒,但哪怕真是 0,它也在宏任务列队,输的是队列不是时间。9
13.2 规范的接缝:谁管什么
书里指了一个精微处:promise 和微任务的时序规则在 ECMAScript 规范里 (引擎必须遵守);而 setTimeout、queueMicrotask 的定时规则在 HTML Living Standard里 ——理论上非浏览器运行时可以给它们不同行为。 现实是 Node 开创了「跟随 HTML 标准」的先例,其他运行时跟进。 这条接缝解释了为什么「setTimeout 最小 4ms」是 HTML 标准的事,而「then 永不同步」 是 ECMAScript 标准的事——两个承诺来自两份文件。10
queueMicrotask 是把代码排进微任务的官方 API——在它有之前,
「想比 setTimeout 更快的异步」只能借 Promise.resolve().then() 的壳;
React 批量更新 DOM 用的就是微任务通道(渲染虽不由 JS 做,浏览器把渲染排在宏任务侧)。
知道这条通道的存在,你也能写「尽快但非同步」的代码了。11
14. 深读四:async/await 的完整语义与类型面
14.1 await 的两步,逐步验证书里的三个样例
await 值的精确语义(书里给的两步):先按 resolve 的同一套规则把值转成 promise
(等价 Promise.resolve(值)),再把函数挂起等它落定。三个样例逐个验算:
console.assert((await "Phantom Blade") === "Phantom Blade");
// 字符串不是 thenable → Promise.resolve 原样兑现 → await 原样返回
console.assert((await Promise.resolve("Falling Devil")) === "Falling Devil");
// 已是 promise → 原样等待 → 返回兑现值
await Promise.reject(new Error("Deadly Mirage")); // throws
// 拒绝 → 在 await 那一行抛出原因——带完整调用栈的普通异常第三点是 await 相对 then 的隐藏升级:拒绝变成了带完整栈的 throw, try/catch/finally 直接通用。promise 时代「错误没有栈、不知出处」的最大抱怨就此了结。12
挂起的函数并没有阻塞线程——「JavaScript 引擎将走到事件循环的下一步」, 其他事件照常触发。await 的隔离带设计也由此说透:它只允许出现在 模块顶层或 async 函数体内。若任何函数都能 await, 「调用一个函数会一鼓作气跑完」的古老假设就废了; async 函数的总返回 promise 把这个「可能暂停」包成了调用方看得见、管得着的形状。13
14.2 async 的两条保证,与「类型层的承诺」
async 函数(function 或箭头函数前加 async)的效果两条(书里照录):14
- return 的值按 resolve 规则包成 promise(已是 promise 则原样)——
return value≡return Promise.resolve(value); - throw 变成拒绝——
throw error≡return Promise.reject(error)。
由此,「调用 async 函数永远拿到 promise」成了类型层的定理:
TypeScript 里 makeNinjaName 的返回类型显示为 Promise<string>。
同时规定了一个不对称:promise 的拒绝原因类型总是 unknown
(和 catch 到的错误一样——「拒绝可以抛任何东西」是检查器承认的现实,
最佳实践是只 reject Error,但语言不强制)。
fetchJson 终态(书里的 Listing 8.8)把两条关键字合在一个函数里, 它值得当模板背:
const fetchJson = async (url: string) => {
const response = await fetch(url);
if (!response.ok) { // 状态码关卡:主动 throw
throw new Error(`Status code ${response.status}`);
}
return response.json(); // 返回 promise:async 会跟随
};
try {
console.log(await fetchJson("jsonplaceholder.typicode.com/posts/1"));
} catch (error) { // 三关的所有错误,一个 catch
console.error(`Request failed: ${error}`);
} finally {
console.log("Request finished");
}API 生态随之转向(书里的时代观察):Node 给经典回调式 fs 之外加了 fs/promises;
util.promisify 能把任何老式回调函数包成返回 promise 的版本。
「返回 promise」从库的设计风格,变成了接口的默认期待。15
一条 lint 纪律顺带记住:promise-function-async 规则要求「凡返回 promise 的函数
都标 async」,动机是错误处理的一致性——调用方期待错误从 promise 里来,
而不是函数同步 throw(那种错误 try/catch 接不住、调用方也没准备)。16
15. 深读五:迭代器协议的完整解剖
15.1 两条约定,逐个符号看清
for...of 能遍历的任何东西,满足两条约定(书里写得很正式):17
约定一:对象上有一个键 Symbol.iterator
Symbol 是 ES2015 引入的「唯一非字符串键类型」——每个 Symbol 值都独一无二,
Symbol.iterator 是 Symbol 类预置的静态属性之一
约定二:object[Symbol.iterator] 是一个函数,
调用它返回一个带 next() 方法的对象;
每次调 next() 要返回 { value, done } 形状的对象满足两者的对象叫 iterable;next() 所在的对象叫 iterator。
for...of 的本质就是:反复调 next(),直到 done 为 true——
把迭代器手动走一遍(书里的 Listing 8.9),等价过程一览无余:
const numbers = [1, 2, 3];
const iterator = numbers[Symbol.iterator]();
let r = iterator.next(); // { value: 1, done: false }
while (!r.done) { console.log(r.value); r = iterator.next(); }
// 打 1、2、3;最后一次 r = { value: undefined, done: true }「数组没什么魔法」是这一节的精神:iterable 是可以自己写的协议, 数组只是内置的一份实现。18
15.2 迭代协议的三个隐藏用户
第 8 章前面埋的「解构/展开也是迭代」值得展开成清单——同一个协议,三处语法共用:
const [a, b, c] = ["a", "b", "c"]; // 数组解构 = 调迭代器取前三个值
func(...iterable); // 展开传参 = 把迭代器依次喂给形参
const array = [...iterable]; // 展开成数组 = 收集全部迭代值
const [first, ...others] = iterable; // 剩余语法 = 取一个、收剩下的警告也在书里:把无限 iterable 整个展开,程序就悬了——
[...randomNumbers] 永远跑不完。迭代的「惰性」是优点也是责任。19
自造 iterable 的秀肌肉例子(Listing 8.10)再读一遍:
const randomNumbers: Iterable<number> = {
[Symbol.iterator]: () => ({
next: () => ({ value: Math.random() }), // 没有 done——永不结束
}),
};
const [num1, num2] = randomNumbers; // 解构取两个就优雅停下iterable 不必是有限的、静态的集合——它可以是「要一个产一个」的懒生产者;
next() 里可以干任何事。生成器要解决的,正是「手写这个协议太啰嗦」的问题。20
16. 深读六:生成器的完整协议与三种高阶用法
16.1 next/yield 的往返协议
生成器函数(function* 定义;箭头函数不行)一调用不执行函数体,
返回一个生成器对象——它既是 iterable 又是 iterator
(generator[Symbol.iterator]() 返回它自己)。next() 与 yield 的往返规则
(书里列了三条,照录):21
next() 一调:函数从上次停点跑到下一个 yield
→ 本次返回 { value: yield 后面的值, done: false }
函数 return(或自然走到末尾)
→ { value: return 后面的值, done: true }
函数 throw
→ 异常从 next() 调用处抛出和 await 的对照再明确一次:await 暂停整个线程(让事件循环走), yield 只暂停自己(控制权立刻回到调 next() 的地方,像 return 一样), 下次 next() 从断点续跑,局部状态原样还在。22
无限随机数生成器(Listing 8.11)是协议的最小演示:
function* randomNumbers() {
while (true) { yield Math.random(); }
}
const g = randomNumbers(); // 函数体一行没跑
const [num1, num2] = g; // 迭代两次:取两个随机数16.2 双向通道:multiplier 的逐步验算
next(值) 能把值塞回生成器——恢复执行时,那个 yield 表达式的值就是塞进去的值。 书里的 multiplier(Listing 8.12),逐步验算:
function* multiplier(): Generator<number, unknown, number> {
let number = 1;
while (true) {
const multiplier = yield number; // 吐出 number,然后把 next(塞入值)交给 multiplier
number *= multiplier;
}
}
const g = multiplier();
g.next().value === 1; // 首次:无塞入值,yield 1;number 还是 1
g.next(5).value === 5; // 塞入 5:1×5=5;yield 5
g.next(4).value === 20; // 塞入 4:5×4=20;yield 20三步各对上书里的一条 assert。这是生成器最少用、但最能说明「yield 是双向路口」的例子; 日常代码里多数生成器只用单向(吐值)那一半。23
16.3 yield* 委托与递归遍历
yield* another() 把迭代委托给另一个生成器(Listing 8.13):
function* warriors() {
yield "Sun Tzu";
yield* ninjas(); // 先把 ninjas 的全部 yield 走完
yield "Genghis Khan";
}
function* ninjas() { yield "Hattori"; yield "Yoshi"; }
[...warriors()]; // ["Sun Tzu", "Hattori", "Yoshi", "Genghis Khan"]委托的用武之地是递归:一个生成器可以把「同一生成器函数的另一份调用」 yield* 下去——「遍历一棵 DOM 树」因此可以写成一个朴素的 for...of, 而不必先把树摊平成数组(摊平既费内存又丢结构)。24
16.4 异步生成器与 for await...of
普通生成器的 yield 不暂停线程——这对「迭代下一个值」是对的语义,
遇上异步数据源就不够了。ES2018 的补全:async 生成器 + 异步迭代协议。
协议的差别只有一处(书里点得清楚):异步迭代器的 next() 返回 promise
(落定值是 { value, done }),键从 Symbol.iterator 换成 Symbol.asyncIterator。
但写代码时这些都被语法藏起来了——async function* 定义、for await...of 消费:
async function* fetchPosts() {
for (let i = 1; i <= 3; i++) {
const response = await fetch(`jsonplaceholder.typicode.com/posts/${i}`);
if (!response.ok) throw new Error(`Status code ${response.status}`);
yield await response.json(); // await 与 yield 合体:真·暂停
}
}
for await (const post of fetchPosts()) { console.log(post); }循环一次的完整时间线:取第 i 篇 → 挂起等网络 → 解析 → 吐出—— 三件事按书写顺序发生,等待期间线程照常处理别的事件。 书里的点睛句照录:「结合了 yield 与 async 和 await,我们终于有了一种 在生成器函数内部真正暂停代码执行的能力。」25
消费端的不对称也要记:异步迭代器没有解构、不能展开传参—— 唯一的语法是 for await...of(和直接调 next())。 现实坐标:流式 API(ReadableStream)也是异步可迭代对象—— 「和网页版大模型对话,文字一段段往外冒」的打字机效果, 背后多半就是一个 for await...of 在逐块渲染。26
16.5 什么时候该写生成器
书里的收尾判断照录并展开——生成器的主业是「把产生值的过程包成 iterable, 让别人好消费」,三个甜区:
- 解构赋值一次取多个生成值(
const [a, b] = generator); - 递归遍历树的 for...of(DOM、文件系统、任何嵌套结构);
- 逐个处理昂贵任务的 for await...of(流式拉取、分批计算)。
「下次写着复杂循环时,问自己:写成生成器会不会更优雅」—— 但别忘了它的历史定位:它曾被寄望与 promise 一起终结回调地狱, async/await 出现后那个主战场没了;今天它是「小众但趁手」的工具, 不是必学的主线。27
17. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| promise 对象化三优点 | Chapter 8: Functions for the future: promises and generators | text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:43(搜「passed around freely」) |
| 三态与一次性 | 同上 | :68(搜「pending, fulfilled, or rejected」) · :73(搜「only change once」) |
| executor 与决议三途 | 同上 | :85(搜「executor」) · :91(搜「throwing an error」) |
| thenable 跟随规则 | 同上 | :105(搜「thenable」) · :132(搜「permanently pending」) |
| resolved vs settled | 同上 | :148(搜「RESOLVED VS. SETTLED」) |
| Promise.withResolvers | 同上 | :174(搜「Promise.withResolvers」) |
| then 返回新 promise | 同上 | :186(搜「returns another promise」) |
| fetch 链与三关错误 | 同上 | :204(搜「Listing 8.3」) · :231(搜「three different places」) |
| finally 与静默拒绝坑 | 同上 | :248(搜「finally method」) · :253(搜「silently ignored」) |
| Promise.all 语义 | 同上 | :276(搜「Listing 8.4」) · :308(搜「all-or-nothing」) |
| any/race/allSettled 对照 | 同上 | :320(搜「allSettled」) · :326(搜「Promise.any returns」) |
| race 掐表与 AbortController | 同上 | :343(搜「Listing 8.5」) · :375(搜「underused feature」) |
| p-limit 边栏 | 同上 | :383(搜「LIMITING CONCURRENCY」) |
| 微任务/宏任务三规则 | 同上 | :400(搜「Of microtasks and macrotasks」) · :444(搜「strictly prioritized」) |
| 输出顺序实验 | 同上 | :409(搜「Listing 8.6」) · :426(搜「end of script」) |
| then 永不同步 | 同上 | :432(搜「never call the then callback synchronously」) |
| 规范分属两份 | 同上 | :459(搜「TIMING ACCORDING TO THE SPECS」) |
| await 的语义与位置限制 | 同上 | :548(搜「halts code execution」) · :583(搜「only allowed」) |
| await 行为三样例 | 同上 | :563(搜「Phantom Blade」) |
| async 两条保证 | 同上 | :606(搜「Automating promise creation」) · :628(搜「always return a promise」) |
| fetchJson 终态 | 同上 | :657(搜「Listing 8.8」) |
| promisify 与 API 迁徙 | 同上 | :682(搜「fs/promises」) |
| promise-function-async | 同上 | :648(搜「promise-function-async」) |
| 生成器简史定位 | 同上 | :691(搜「didn't work out that way」) |
| 迭代器协议两条件 | 同上 | :713(搜「Symbol.iterator」) |
| 手动迭代数组 | 同上 | :745(搜「Listing 8.9」) |
| 解构=迭代的三个场合 | 同上 | :764(搜「destructuring syntax」) |
| 无限 iterable 示例 | 同上 | :788(搜「Listing 8.10」) |
| 生成器函数与生成器对象 | 同上 | :809(搜「generator function」) · :826(搜「GENERATOR TERMINOLOGY」) |
| next/yield 协议三条 | 同上 | :826(搜「iterator result returned from next」) |
| multiplier 双向通道 | 同上 | :867(搜「Listing 8.12」) |
| yield* 委托与递归 | 同上 | :888(搜「Listing 8.13」) · :919(搜「recursion」) |
| 异步迭代协议 | 同上 | :926(搜「Async iterables and async generators」) |
| fetchPosts 异步生成器 | 同上 | :944(搜「Listing 8.14」) |
| for await 的消费限制 | 同上 | :970(搜「aren't quite as versatile」) |
| ReadableStream 指认 | 同上 | :974(搜「smooth flow of text」) |
| 生成器适用判断 | 同上 | :984(搜「main use case」) |
| 全章总结 | 同上 | :996(搜「Summary」) |
Footnotes
-
出处:「Chapter 8: Functions for the future: promises and generators」第 19–50 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:43,搜「passed around freely」)。「an object representation of an event」及三个优点清单。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 68–76 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:69,搜「pending, fulfilled, or rejected」)。三态定义;fulfilled/rejected 合称 settled 见第 71 段。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 73–76 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:73,搜「only change once」)。「Once it's settled, its state and value are set permanently」。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 79–95 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:85,搜「The constructor returns a promise that starts in」)。executor 定义、同步执行、三条改变状态的路径(第 91–95 段)。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 102–110 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:105,搜「with the value undefined. But if you call」)。thenable 定义与「subscribe」机制。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 113–130 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:113,搜「Listing 8.2」)。跟随行为的带数示例。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 148–158 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:148,搜「RESOLVED VS. SETTLED」)。规范术语分辨与口语误用提醒。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 163–181 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:174,搜「Happily, ES2024 introduced a convenient new static method」)。ES2024 新 API 与旧 hack 的对照。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 186–191 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:188,搜「returns another promise」)。「then has one more trick up its sleeve」。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 190–201 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:228,搜「shorthand for promise.then」)。catch/finally 作为 then 简写;一个 catch 接三处错误的清单见第 231–240 段。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 241–262 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:248,搜「finally method」)。ES2018 的 finally 与 try/catch 类比。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 253–257 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:254,搜「silently ignored」);与第 259–261 段(搜「can't modify the state」)。两个工程教训原文。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 266–307 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:276,搜「Listing 8.4」)。Promise.all 的完整示例。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 308–317 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:318,搜「all-or-nothing」)。全好才算好的定位与输出顺序保证(第 310–313 段)。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 339–359 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:343,搜「Listing 8.5」)。race 掐表完整代码。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 375–380 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:378,搜「underused feature」)。没有 AbortController,超时后请求还在后台耗资源。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 320–338 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:335,搜「Promise.any is newer and lesser-used, but useful in」)。any 的缓存+数据库例子与 allSettled 组合思路(第 322–325 段)。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 383–395 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:383,搜「LIMITING CONCURRENCY」)。四种方法管不了有界并发及 p-limit 家族。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 400–458 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:440,搜「job queue」)。job queue/microtask 与 task/macrotask 的命名,及「strictly prioritized」(第 444 段)。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 409–425 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:409,搜「Listing 8.6」)。输出顺序实验与「reliably get this output」。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 432–439 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:432,搜「You might be surprised that the then callback」)。then 永不同步的理由与 blessing 定性。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 436–439 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:438,搜「minimum delay of 4 milliseconds」)。setTimeout 最小延迟规定。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 459–467 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:459,搜「TIMING ACCORDING TO THE SPECS」)。两份规范的分工与 Node 跟随 HTML 的先例。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 452–457 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:456,搜「queueMicrotask」)。官方微任务 API 与 React 批量更新的用例。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 548–561 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:549,搜「implicitly converts」)。await 的两步语义与「non-blocking」论证(第 553–554 段)。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 574–582 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:577,搜「complete stack trace」)。await 行 throw 与「one of the biggest gripes」的解决。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 583–598 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:585,搜「only allowed」)。两个合法位置与「clever solution」的设计理由。 ↩ ↩2 -
出处:「Chapter 8: Functions for the future: promises and generators」第 593–597 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:594,搜「type": "module"」)。声明 ESM 的三种方式与 .mts 的选择。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 606–646 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:628,搜「Returned values are converted to promises using the」)。async 的两条规则与 Promise<string> 类型。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 682–688 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:684,搜「fs/promises」)。API 从回调迁往 promise 的行业转向与 promisify。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 647–651 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:648,搜「promise-function-async」)。规则动机:错误该从 promise 来而非同步 throw。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 691–707 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:704,搜「didn't work out that way」)。生成器期望落空史与「a bit of an oddity」的现状定位。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 713–739 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:727,搜「Symbol.iterator」)。iterable 的两条件与 Symbol 介绍。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 745–762 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:745,搜「Listing 8.9」)。手动迭代 walk-through 与「nothing magical about arrays」。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 764–783 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:770,搜「This is commonly referred to as the array」)。解构/展开/剩余皆是迭代的三个场合。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 788–803 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:788,搜「Listing 8.10」)。无限随机数 iterable;「be careful that the iterable is finite」的提醒见第 785 段。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 809–823 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:810,搜「function keyword followed by *」)。生成器函数定义与「既是 iterable 又是 iterator」的生成器对象。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 826–840 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:833,搜「iterator result returned from next」)。yield/return/throw 三种 next 结果形态。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 828–835 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:828,搜「pauses the function's execution」)。yield 与 await 的暂停语义对比。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 867–885 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:871,搜「Listing 8.12」)。multiplier 的 1、5、20 三断言与双向通道说明。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 888–917 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:893,搜「Listing 8.13」)。yield* 委托与四行输出的对照。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 919–924 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:919,搜「recursion」)。yield* 递归遍历 DOM 树的用例。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 926–942 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:931,搜「ES2018 introduced async generators」)。异步迭代协议与键名 Symbol.asyncIterator。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 944–966 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:968,搜「actually pause code execution」)。fetchPosts 完整例子与点睛句。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 970–975 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:970,搜「aren't quite as versatile」)。异步迭代器的消费限制(无解构、无展开)。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 974–981 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:976,搜「smooth flow of text」)。ReadableStream 与网页大模型打字机效果的联系。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 984–993 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:986,搜「main use case」)。生成器的适用判断与原书的收尾建议。 ↩ -
出处:「Chapter 8: Functions for the future: promises and generators」第 121–130 段(
text/11-ch08-chapter-8-functions-for-the-future-promises-and-.txt:122,搜「was fulfilled with value」);与:426(搜「end of script」);与:882(搜「multiplierGenerator」)。三组可复现断言。 ↩