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

476 阅读3分钟

1️⃣ 什么是 Promise?

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

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 的使用

基本链式调用

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 执行完成。

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

等价形式:

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

5️⃣ async/await 的底层原理

✅ 本质上是 Promise + 语法糖

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

等价于:

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

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


6️⃣ await 的行为细节

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

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

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

await 1; // 相当于 await Promise.resolve(1);
await Promise.reject('error'); // 抛出错误

7️⃣ async/await 和 Promise 的对比

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

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

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

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

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

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

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

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 为什么只执行一次?

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 为何只输出一次“开始”?

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 不生效

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

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

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

正确写法

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 进行并发