async/await 执行模型

深入理解 async/await 的本质、await 的暂停机制,以及 async 函数执行过程中的微任务队列变化。


async/await 不是什么

很多工程师把 async/await 当成 JavaScript 独有的异步语法,甚至认为它和 Promise 是两套并行的机制。这是一个常见的误解。

async/await 是 Promise 的语法糖,不是独立于 Promise 的新机制。

  • async 函数返回一个 Promise
  • await 等待一个 Promise resolved(或 rejected),然后以那个值(或错误)继续执行

理解这一点,是掌握 async/await 的前提。


async 函数的本质

async 函数的返回值

任何 async 函数的返回值,都会被 JavaScript 引擎自动包装成 Promise:

async function foo() {
  return 42;
}

// 等价于:
function foo() {
  return Promise.resolve(42);
}

如果 async 函数抛出异常,返回的 Promise 会被 rejected:

async function foo() {
  throw new Error('oops');
}

// 等价于:
function foo() {
  return Promise.reject(new Error('oops'));
}

async 函数的并行执行

async 函数是立即执行的,只是返回值是 Promise:

async function fetchUser() {
  console.log('fetchUser start');
  const response = await fetch('/api/user');
  return response.json();
}

async function fetchPosts() {
  console.log('fetchPosts start');
  const response = await fetch('/api/posts');
  return response.json();
}

// 两个函数会并发执行
const userPromise = fetchUser();
const postsPromise = fetchPosts();

// 等待两个都完成
const [user, posts] = await Promise.all([userPromise, postsPromise]);

await 的暂停机制

这是理解 async/await 执行模型的核心。

await 不是"阻塞等待",而是暂停当前函数的执行,将后续代码注册为微任务

async function foo() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}

console.log('3');
foo();
console.log('4');

执行过程:

  1. console.log('3') 同步输出
  2. 调用 foo()console.log('1') 输出,await Promise.resolve() 暂停 foo,将 console.log('2') 注册为微任务
  3. console.log('4') 同步输出
  4. 微任务执行,console.log('2') 输出

所以输出是:3, 1, 4, 2


await 之后的代码是微任务

await 后面的代码,不是"等 await 完成后立即执行",而是"作为微任务加入队列"。

async function foo() {
  console.log('a');
  await bar();
  console.log('b');
}

function bar() {
  return Promise.resolve();
}

await bar() 做了什么:

  1. 调用 bar(),返回 Promise
  2. console.log('b') 作为微任务加入队列
  3. 暂停 foo 的执行,控制权返回给调用者

多个 await 的执行顺序

async function foo() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
  await Promise.resolve();
  console.log('3');
  await Promise.resolve();
  console.log('4');
}

foo().then(() => console.log('done'));

// 输出: 1, 2, 3, 4, done

每个 await 都会产生一个微任务,它们按顺序执行。


async/await 与错误处理

try/catch

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('fetch failed:', error);
    throw error;
  }
}

catch 与 Promise.all

// 错误:Promise.all 不会等待 catch
const [a, b] = await Promise.all([
  fetchA().catch(e => null),
  fetchB().catch(e => null)
]);

常见陷阱

陷阱一:串行 instead of 并行

// 串行:慢
async function wrong() {
  const a = await fetchA();
  const b = await fetchB();
  return [a, b];
}

// 并行:快
async function right() {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
  return [a, b];
}

陷阱二:循环中的 await

// 串行执行
async function wrong(urls) {
  const results = [];
  for (const url of urls) {
    const result = await fetch(url);
    results.push(result);
  }
  return results;
}

// 并行执行
async function right(urls) {
  const promises = urls.map(url => fetch(url));
  return Promise.all(promises);
}

这一章想说的

async/await 是 Promise 的语法糖。理解这一点,就不会写出"在 async 函数里用 .then() 是多余的"这种错误认知。

await 的本质是暂停当前函数的执行,将后续代码注册为微任务。这是 JavaScript 单线程 + 事件循环模型的一部分,不是"阻塞等待"。

写出高效异步代码的关键是:需要并行的时候,不要串行。学会用 Promise.all 批量发起异步操作。


延展阅读


实践练习

练习:优化下列代码

async function getUserData(userId) {
  const user = await fetch(`/api/users/${userId}`).then(r => r.json());
  const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
  const comments = await fetch(`/api/users/${userId}/comments`).then(r => r.json());
  return { user, posts, comments };
}

这段代码的性能问题是什么?如何优化?