async/await 原理与 Promise 对照

1. 核心原理

async/await 是基于 Promise 的语法糖。它没有改变 JavaScript 的异步模型,只是让 Promise 链可以写得更接近同步代码。

  • async:保证函数返回一个 Promise。
  • await:等待一个 Promise 的结果,并暂停当前 async 函数后续代码的执行。
  • await 后面的代码:相当于放进 .then() 回调,会在微任务中继续执行。
  • try/catch:相当于 Promise 链末尾的 .catch()

先看一个最小对照。

async/await 写法

async function getUserName() {
  const user = await getUser();
  return user.name;
}

Promise 写法

function getUserName() {
  return getUser().then((user) => {
    return user.name;
  });
}

两段代码的核心流程完全相同:

调用 getUser()
等待 Promise 成功
读取 user.name
返回一个以 user.name 为结果的 Promise

2. 执行顺序对照

面试中经常考 await 前后代码的执行时机。

async/await 写法

console.log('script start');

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

async1();

console.log('script end');

等价的 Promise 写法

console.log('script start');

function async1() {
  console.log('async1 start');

  return async2().then(() => {
    console.log('async1 end');
  });
}

function async2() {
  console.log('async2');
  return Promise.resolve();
}

async1();

console.log('script end');

两段代码的输出相同:

script start
async1 start
async2
script end
async1 end

执行过程如下:

  1. 输出 script start
  2. 调用 async1(),同步输出 async1 start
  3. 调用 async2(),函数体立即执行,同步输出 async2
  4. await 后面的代码等价于 .then() 回调,被加入微任务队列。
  5. 主线程继续执行,同步输出 script end
  6. 当前同步代码结束后,执行微任务,输出 async1 end

关键不是“看到 await 就立即异步”,而是:

await 右侧的表达式会先执行,await 后面的代码才会延后到微任务中执行。

3. 错误处理对照

async/await 可以用 try/catch 统一处理多个异步步骤的错误。

async/await 写法

async function fetchUserPosts() {
  try {
    const user = await getUser();
    const posts = await getPosts(user.id);
    return posts;
  } catch (error) {
    // 可以捕获 getUser 或 getPosts 的失败
    console.error(error);
    return [];
  }
}

等价的 Promise 写法

function fetchUserPosts() {
  return getUser()
    .then((user) => {
      return getPosts(user.id);
    })
    .then((posts) => {
      return posts;
    })
    .catch((error) => {
      // 可以捕获 getUser 或 getPosts 的失败
      console.error(error);
      return [];
    });
}

对应关系是:

async/awaitPromise
await getUser()getUser().then(...)
第二个 await第二个 .then(...)
try/catch.catch(...)
return posts.then()return posts

如果希望调用方继续处理错误,两种写法都不要在当前函数中把错误“吞掉”:

// async/await:捕获后重新抛出
catch (error) {
  console.error(error);
  throw error;
}

// Promise:捕获后返回 rejected Promise
.catch((error) => {
  console.error(error);
  return Promise.reject(error);
});

4. 串行执行对照

当 B 必须等 A 完成后才能开始时,需要串行执行。

async/await 写法

async function serial() {
  const a = await fetchA();
  const b = await fetchB();
  return [a, b];
}

等价的 Promise 写法

function serial() {
  return fetchA().then((a) => {
    return fetchB().then((b) => {
      return [a, b];
    });
  });
}

执行过程是:

开始请求 A → A 完成 → 开始请求 B → B 完成

两种写法中,fetchB() 都是在 A 完成后才被调用,因此总耗时大约是两个请求耗时之和。

5. 并行执行对照

如果 A 和 B 互不依赖,应当同时启动。

async/await 写法

async function parallel() {
  const promiseA = fetchA();
  const promiseB = fetchB();

  const a = await promiseA;
  const b = await promiseB;

  return [a, b];
}

等价的 Promise 写法

function parallel() {
  const promiseA = fetchA();
  const promiseB = fetchB();

  return Promise.all([promiseA, promiseB]);
}

也可以在 async 函数中直接使用 Promise.all

async function parallel() {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
  return [a, b];
}

执行过程是:

开始请求 A ─────→ A 完成
开始请求 B ───────────→ B 完成
                    得到全部结果

这里实现并行的关键不是 await,而是 fetchA()fetchB() 在等待结果之前就都已经被调用。

6. 返回值与异常对照

返回普通值

// async/await 写法
async function getValue() {
  return 42;
}

// 等价的 Promise 写法
function getValue() {
  return Promise.resolve(42);
}

两种写法调用后得到的都是一个以 42 为结果的 Promise。

抛出异常

// async/await 写法
async function fail() {
  throw new Error('失败');
}

// 等价的 Promise 写法
function fail() {
  return Promise.reject(new Error('失败'));
}

两种写法调用后得到的都是 rejected Promise,而不是直接把异常抛给函数调用处。

7. 面试总结

可以把常见写法记成下面的对应关系:

async/awaitPromise
async function返回 Promise 的普通函数
const value = await promisepromise.then((value) => ...)
try/catch.catch()
return valuePromise.resolve(value)
throw errorPromise.reject(error)
并行等待多个任务Promise.all()

面试时可以这样回答:

async/await 是基于 Promise 的语法糖。async 函数一定返回 Promise;await 会先执行右侧表达式,再暂停当前函数,把后续代码放到微任务中继续执行。连续的 await 对应连续的 .then()try/catch 对应 .catch()。如果多个异步任务互不依赖,应先同时启动,再使用 Promise.all() 等待结果,避免无意中串行执行。

补充:ES2022 允许在 ES Module 顶层使用 await,无需包裹在 async 函数中。