文章

async-await实现原理深度解析

揭开 async/await 的语法糖面纱——底层是 Generator + Promise 自动执行器,await 等价于 yield + then。

async-await实现原理深度解析

一句话概括

async/await 不是新魔法,是 Generator + Promise 自动执行器的语法糖——把 yield 换成 await,把手动 .next() 换成引擎自动推进,让你用同步方式写异步代码。


核心知识点

1. async 函数的返回值永远是 Promise

1
2
3
4
5
async function fn1() { return 42; }
fn1(); // Promise { 42 }——自动包装

async function fn2() { throw new Error('boom'); }
fn2(); // Promise { <rejected> Error: boom }

规则简单到离谱:return 普通值 → Promise.resolve 包装;throw → Promise.reject 包装;什么都不 return → Promise.resolve(undefined)

2. await 暂停的是 async 函数,不是主线程

1
2
3
4
5
6
7
8
9
console.log('1');
async function demo() {
  console.log('2');
  await Promise.resolve();
  console.log('3'); // 微任务
}
demo();
console.log('4');
// 输出:1 → 2 → 4 → 3

await 之后的代码等价于 .then(() => { ... })——被推入微任务队列,在同步代码跑完后执行。

3. Generator 版 async/await(关键!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function* myAsync() {
  const a = yield Promise.resolve(1);
  const b = yield Promise.resolve(a + 2);
  return b;
}

// 自动执行器
function run(gen) {
  return new Promise((resolve, reject) => {
    const g = gen();
    function step(next) {
      let result;
      try { result = g[next](); } catch (e) { return reject(e); }
      if (result.done) return resolve(result.value);
      Promise.resolve(result.value).then(
        v => step('next'),
        e => g.throw(e)
      );
    }
    step('next');
  });
}

run(myAsync).then(console.log); // 3

这就是 async/await 的真相:Generator 负责暂停/恢复,Promise 负责异步,自动执行器负责推动

4. 错误处理两种姿势

1
2
3
4
5
6
7
8
9
10
11
12
13
// 姿势一:try/catch(推荐)
async function fn() {
  try {
    const data = await fetchData();
  } catch (e) {
    console.error('请求失败', e);
  }
}

// 姿势二:.catch()
async function fn2() {
  const data = await fetchData().catch(e => null);
}

try/catch 能抓 await 后面的 rejected Promise,因为 reject 被转换为 Generator 的 throw

5. 并发 vs 串行 —— 性能差一个数量级

1
2
3
4
5
6
7
// ❌ 串行:总耗时 = t1 + t2 + t3
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

// ✅ 并发:总耗时 ≈ max(t1, t2, t3)
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

await 之间没有依赖就用 Promise.all,这是最基本的性能素养。


「其实你每天都在用」

1. 任何带 await 的网络请求

1
const users = await fetch('/api/users').then(r => r.json());

2. React Server Components / Next.js

1
2
3
4
export default async function Page() {
  const data = await db.query('SELECT ...');
  return <div>{data}</div>;
}

3. 带重试的请求封装

1
2
3
4
5
6
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try { return await fetch(url).then(r => r.json()); }
    catch (e) { if (i === retries - 1) throw e; }
  }
}

4. for-await-of 遍历异步流

1
2
3
for await (const chunk of stream) {
  process(chunk);
}

5. 动态 import

1
const module = await import('./heavy-module.js');

常见误解(FAQ)

❌ 误区 1:「async 函数里所有代码都是异步的」

只有 await 之后的代码是异步的(微任务)。await 之前的代码和 async 函数调用本身都是同步执行的。

❌ 误区 2:「await 阻塞了主线程」

await 暂停的是当前 async 函数的执行上下文,不是主线程。主线程继续跑别的代码。把 await 想象成”我先离开一下,你们继续”。

❌ 误区 3:「forEach 里写 await 会串行执行」

既不会串行也不会正确等待。forEach 的回调是同步执行的,里面的 await 不会阻塞循环——所有请求几乎同时发出,但 forEach 本身不等结果。要串行用 for...of,要并发用 Promise.all + map

❌ 误区 4:「async 函数比 Promise.then 快」

本质一样。async/await 编译后就是 Promise 链,性能完全等价。选哪个是代码可读性的问题,不是性能问题。


一句话总结

async/await 用”暂停”的假象掩盖了”回调”的真相——但这个假象足够好用,让异步代码读起来像同步,写起来像呼吸。

本文由作者按照 CC BY 4.0 进行授权