Skip to content

《彻底搞懂 async&await:写出优雅异步代码的终极指南》

✍️天畅🕐2025-05-20

1️⃣ 什么是 Promise?

Promise 是 JavaScript 为了解决回调地狱(callback hell)问题而引入的一种异步编程解决方案。

ts
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (success) {
    resolve(result);
  } else {
    reject(error);
  }
});
  • resolve(value):代表成功,状态变为 fulfilled。

  • reject(error):代表失败,状态变为 rejected。

Promise 有三种状态:

状态含义
pending等待中
fulfilled已成功
rejected已失败

2️⃣ Promise 的使用

基本链式调用

ts
promise
  .then((res) => console.log(res))
  .catch((err) => console.error(err))
  .finally(() => console.log('done'));
  • .then():接收成功回调。

  • .catch():接收失败回调。

  • .finally():无论成功失败都会执行。

3️⃣ Promise 的特性

  • 一旦状态改变,就不可再次更改(不可逆)。

  • then/catch 注册的是微任务

  • 多次 .then() / .catch() 不会导致 Promise 重复执行。

4️⃣ async / await 是什么?

  • async 是用来声明一个异步函数,它会自动将返回值包装成 Promise

  • await 是用来等待一个 Promise 执行完成。

ts
async function getData() {
  try {
    const result = await fetch('/api/data');
    console.log(result);
  } catch (err) {
    console.error(err);
  }
}

等价形式:

ts
fetch('/api/data')
  .then((res) => console.log(res))
  .catch((err) => console.error(err));

5️⃣ async/await 的底层原理

✅ 本质上是 Promise + 语法糖

ts
async function foo() {
  const res = await Promise.resolve('hello');
  return res;
}

等价于:

ts
function foo() {
  return Promise.resolve('hello');
}
  • await 会暂停当前函数的执行,等到 Promise 结果返回后再继续。

  • await 后面可以跟普通值、Promise 或 async 函数。

6️⃣ await 的行为细节

  • 如果 await 后是普通值,会自动包装成 Promise。

  • 如果是 Promise,会等待该 Promise resolve 后再继续执行。

  • await 后的表达式执行异常,相当于 Promise.reject(),需用 try/catch 捕获。

ts
await 1; // 相当于 await Promise.resolve(1);
await Promise.reject('error'); // 抛出错误
ts
async function test() {
  try {
    const result = await Promise.reject('失败了');
    console.log(result);
  } catch (error) {
    console.error('捕获到了错误:', error); // ✅ 正确捕获
  }
}
  • 附上一错误捕获工具函数
ts
async function withCatch<T>(promise: Promise<T>): Promise<[T | null, any]> {
  try {
    const result = await promise;
    return [result, null];
  } catch (err) {
    return [null, err];
  }
}

async function process() {
  const [res1, err1] = await withCatch(step1());
  if (err1) return console.error('step1 失败', err1);

  const [res2, err2] = await withCatch(step2());
  if (err2) return console.error('step2 失败', err2);

  const [res3, err3] = await withCatch(step3());
  if (err3) return console.error('step3 失败', err3);
}

7️⃣ async/await 和 Promise 的对比

特性Promiseasync/await
可读性回调链可能较长代码更清晰,类似同步流程
错误处理.catch() 捕获try...catch 捕获
并发处理.all(), .race()可用 Promise.all() 搭配 await
使用难度略高较低,适合结构化异步代码

8️⃣ 并发 & 串行执行技巧

并发(同时执行多个异步任务):

ts
await Promise.all([task1(), task2(), task3()]);

串行(一个一个顺序执行):

ts
await task1();
await task2();
await task3();

Promise.allSettled()说明

ts
Promise.allSettled(promises: Promise<any>[])

返回一个 Promise,当所有的 promises 都已 完成(不管成功或失败) 时,返回一个数组,数组中的每一项是:

  • { status: "fulfilled", value: any } — 如果对应的 Promise 成功。
  • { status: "rejected", reason: any } — 如果对应的 Promise 失败。
ts
const p1 = Promise.resolve('🍎 Apple');
const p2 = Promise.reject('🍌 Banana error');
const p3 = new Promise((resolve) => setTimeout(() => resolve('🍇 Grape'), 500));

Promise.allSettled([p1, p2, p3])
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`任务 ${index + 1} 成功:`, result.value);
      } else {
        console.log(`任务 ${index + 1} 失败:`, result.reason);
      }
    });
  });
  
任务 1 成功: 🍎 Apple
任务 2 失败: 🍌 Banana error
任务 3 成功: 🍇 Grape

🔍 和 Promise.all() 区别

方法出错是否中断返回内容
Promise.all()是(第一个失败就中断)成功值数组或抛出错误
Promise.allSettled()否(全部都等)每个任务的状态与结果组成的数组

9️⃣ 实现简易版 Promise(简化版)

ts
class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = null;
    this.reason = null;
    this.onResolved = [];
    this.onRejected = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolved.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejected.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    } else if (this.state === 'rejected') {
      onRejected(this.reason);
    } else {
      this.onResolved.push(() => onFulfilled(this.value));
      this.onRejected.push(() => onRejected(this.reason));
    }
  }
}

🔟 常见面试题分析

❓ Promise 为什么只执行一次?

ts
const p = new Promise(resolve => {
  console.log('start');
  resolve();
});

p.then(() => console.log('then1'));
p.then(() => console.log('then2'));

✅ 输出:

start
then1
then2

解释:Promise 执行器立即执行,then 不会触发多次执行,而是订阅回调。

❓ async/await 为何只输出一次“开始”?

ts
const a = new Promise(resolve =>
  setTimeout(() => {
    console.log('开始');
    resolve('done');
  }, 1000)
);

async function b() {
  await a;
  console.log('结束b');
}

function c() {
  a.then(() => {
    console.log('结束c');
  });
}

b();
c();

✅ 输出:

开始
结束c
结束b

✅ 原因:a 是同一个 Promise,只执行一次定时器。多次 await.then() 不会重复触发执行器。


❓ 为什么下述代码 await 不生效

ts
const e = async () => {
  await Promise.resolve(setTimeout(() => {
    console.log('xx');
  }, 1000))
  console.log('xxxx');
}

setTimeout 本身 不是一个 Promise,它返回的是一个定时器 ID(数字),所以你 Promise.resolve(setTimeout(...)) 实际上是:

ts
await Promise.resolve(42); // 假设返回的 timer id 是 42

正确写法

ts
await new Promise((resolve) => {
  setTimeout(() => {
    console.log('xx');
    resolve();
  }, 1000);
});

✅ 总结

内容总结
Promise用于异步操作,状态不可逆
async/await是基于 Promise 的语法糖,更符合同步编程习惯
await 原理会暂停当前函数的执行,等待 Promise resolve 结果
多次 then不会重复执行 Promise,都会接收相同的结果
异常处理Promise 用 .catch(),async/await 用 try/catch
并发处理使用 Promise.all 进行并发