← Frontend

Promise

Promise 原理、事件循环、三种状态、实例方法、静态方法、手写实现、async/await 详解

目录

章节 核心内容
一、事件循环 宏任务、微任务、执行顺序、Node.js 事件循环
二、什么是 Promise Promise 的定义与核心特点、回调地狱
三、Promise 的三种状态 pending、fulfilled、rejected,状态流转规则
四、构造函数与参数详解 executor、resolve、reject 参数说明
五、Promise 实例方法 then、catch、finally
六、Promise 静态方法 resolve、reject、all、allSettled、race、any
七、注意点与常见坑 5 个常见坑点
八、手写 Promise 实现 Promise/A+ 规范实现
九、async / await 语法糖、错误处理、并行优化、顶层 await
十、对比总结 Promise vs async/await,选用建议

一、事件循环(Event Loop)

JavaScript 是 单线程 语言,一次只能做一件事。事件循环是 JavaScript 实现 异步非阻塞 的核心机制。

1.1 为什么需要事件循环?

单线程意味着所有任务必须排队执行,如果前一个任务耗时很长,后面的任务就得一直等:

// ❌ 同步阻塞:用户点击按钮后页面卡死 5 秒
console.log('开始');
sleep(5000);              // 假设这是个同步的耗时操作
console.log('结束');       // 5 秒后才输出

为了解决这个问题,JavaScript 把任务分为两类:

  • 同步任务(Synchronous):立即执行,在主线程上排队
  • 异步任务(Asynchronous):不进入主线程,而是交给浏览器其他线程处理,完成后将回调放入任务队列

1.2 事件循环的核心组成

┌───────────────────────────────┐
│           Call Stack          │  ← 主线程,执行同步代码
│         (调用栈)              │
└───────────────────────────────┘
              ↑
              │ 同步代码执行完
              ↓
┌───────────────────────────────┐
│        Event Loop             │  ← 不断检查栈是否为空
│       (事件循环)              │     空则从队列取任务执行
└───────────────────────────────┘
              ↑
              │
              ↓
┌───────────────────────────────┐
│        Task Queues            │
│   ┌─────────────────────────┐ │
│   │  Microtask Queue (微任务)│ │  ← Promise.then、MutationObserver
│   └─────────────────────────┘ │
│   ┌─────────────────────────┐ │
│   │  Macrotask Queue (宏任务)│ │  ← setTimeout、setInterval、I/O
│   └─────────────────────────┘ │
└───────────────────────────────┘

1.3 宏任务与微任务

类型 微任务(Microtask) 宏任务(Macrotask)
典型代表 Promise.then/catch/finally setTimeout、setInterval
queueMicrotask() setImmediate(Node.js)
MutationObserver I/O、UI 渲染
process.nextTick(Node.js)
requestAnimationFrame
await 后的执行代码
执行时机 当前宏任务结束后立即执行 下一轮事件循环开始时执行
优先级 高 低

⚠️注意: 人工合成的事件派发,即直接在代码里写的dom.click()或dispatchEvent(),相对于浏览器而言并不是真正的用户交互,会被当作同步任务执行

1.4 事件循环的执行顺序

1. 执行同步代码(这本身就是一个宏任务)
2. 检查微任务队列,有则依次执行所有微任务
3. UI 渲染(如果需要)
4. 执行下一个宏任务
5. 回到步骤 2

口诀:同步 → 微任务 → 宏任务

1.5 Node.js 事件循环

Node.js 的事件循环基于 libuv 库实现,与浏览器事件循环有显著差异。

Node.js 事件循环的 6 个阶段

   ┌───────────────────────────┐
┌─>│           timers          │  ← setTimeout、setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← I/O 回调(延迟到下一轮)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← 轮询阶段,处理 I/O 事件
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  ← close 事件回调
   └───────────────────────────┘
阶段 说明
timers 执行 setTimeout / setInterval 的回调
pending callbacks 执行上一轮循环中未执行的 I/O 回调
idle, prepare 仅系统内部使用
poll 等待新的 I/O 事件,执行 I/O 回调
check 执行 setImmediate 回调
close callbacks 执行 socket.on('close') 等关闭回调

Node.js 与浏览器事件循环的区别

差异点 浏览器 Node.js
微任务执行时机 每个宏任务结束后 每个阶段结束后
process.nextTick 不支持 优先级最高(在微任务之前)
setImmediate 不支持 支持,在 check 阶段执行
setTimeout(fn, 0) 最小 4ms 最小 1ms

process.nextTick 与 Promise 微任务

Node.js 中微任务执行顺序:process.nextTick → Promise.then

// Node.js 环境
console.log('start');

process.nextTick(() => {
  console.log('nextTick');
});

Promise.resolve().then(() => {
  console.log('promise');
});

console.log('end');

// 输出:start → end → nextTick → promise

setTimeout vs setImmediate

// 在 I/O 回调中,setImmediate 总是先于 setTimeout
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});

// 输出:timeout → immediate(顺序可能颠倒)
// 但在 I/O 回调中:immediate → timeout(setImmediate 必定先执行)

原因: I/O 回调在 poll 阶段执行,下一个阶段是 check(setImmediate),然后才回到 timers(setTimeout)。

Node.js 事件循环执行顺序

1. 执行同步代码
2. 执行 process.nextTick 队列(清空为止)
3. 执行微任务队列(Promise.then 等,清空为止)
4. 进入事件循环各阶段
   - 每个阶段结束后,都会执行步骤 2-3

完整示例

// Node.js 环境
console.log('1');

setTimeout(() => {
  console.log('2');
  process.nextTick(() => console.log('3'));
  Promise.resolve().then(() => console.log('4'));
}, 0);

process.nextTick(() => {
  console.log('5');
});

Promise.resolve().then(() => {
  console.log('6');
});

setImmediate(() => {
  console.log('7');
});

console.log('8');

// 输出:1 → 8 → 5 → 6 → 2 → 3 → 4 → 7

解析:

1. 同步代码:输出 1、8
2. process.nextTick 队列:输出 5
3. 微任务队列:输出 6
4. timers 阶段(setTimeout):输出 2
5. setTimeout 内的 nextTick:输出 3
6. setTimeout 内的微任务:输出 4
7. check 阶段(setImmediate):输出 7

1.6 经典面试题解析

题目一:基础执行顺序

console.log('1');                    // 同步

setTimeout(() => {
  console.log('2');                  // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3');                  // 微任务
});

console.log('4');                    // 同步

// 输出顺序:1 → 4 → 3 → 2

解析:

  1. 同步代码先执行:输出 1、4
  2. setTimeout 回调放入宏任务队列
  3. Promise.then 回调放入微任务队列
  4. 同步代码执行完,检查微任务队列,执行输出 3
  5. 微任务清空,执行下一个宏任务,输出 2

题目二:嵌套 Promise

console.log('start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('promise1');
  })
  .then(() => {
    console.log('promise2');
  });

console.log('end');

// 输出:start → end → promise1 → promise2 → setTimeout

解析:

  • 第二个 .then() 是在第一个 .then() 执行后才加入微任务队列的
  • 微任务队列会清空为止,所以两个 Promise 回调都在 setTimeout 之前执行

题目三:复杂的混合场景

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
}, 0);

new Promise((resolve) => {
  console.log('4');     // executor 同步执行!
  resolve();
}).then(() => {
  console.log('5');
});

console.log('6');

// 输出:1 → 4 → 6 → 5 → 2 → 3

详细步骤:

第一轮事件循环:
├── 同步代码执行
│   ├── console.log('1')        → 输出 1
│   ├── setTimeout              → 回调放入宏任务队列
│   ├── new Promise executor    → 输出 4,resolve()
│   └── console.log('6')        → 输出 6
├── 微任务队列检查
│   └── Promise.then 回调       → 输出 5
└── 第一轮结束

第二轮事件循环:
├── 执行宏任务(setTimeout 回调)
│   ├── console.log('2')        → 输出 2
│   └── Promise.then            → 放入微任务队列
├── 微任务队列检查
│   └── Promise.then 回调       → 输出 3
└── 第二轮结束

题目四:async/await 与事件循环

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

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

console.log('script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

async1();

new Promise((resolve) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
});

console.log('script end');

// 输出:script start → async1 start → async2 → promise1 → script end
//      → async1 end → promise2 → setTimeout

关键点: await 后面的代码等价于 .then(),会放入微任务队列。

1.7 关键记忆点

要点 说明
Promise executor 同步执行 new Promise(executor) 中的 executor 是同步的
.then() 是微任务 Promise 的回调总是微任务
微任务优先级高于宏任务 每个宏任务结束后,必须清空所有微任务
微任务可以产生新微任务 会继续执行,直到队列清空
setTimeout(fn, 0) 不是立即执行 至少要等待 4ms(浏览器限制)且要等宏任务轮次

1.8 实际应用场景

// 场景:确保 DOM 更新后再执行操作
this.setState({ data: newData });
// ❌ 这里可能还拿不到最新的 DOM
console.log(this.refs.list.scrollHeight);

// ✅ 使用微任务确保在渲染前执行
Promise.resolve().then(() => {
  console.log(this.refs.list.scrollHeight);  // DOM 已更新
});

// ✅ 或使用宏任务确保在渲染后执行
setTimeout(() => {
  console.log(this.refs.list.scrollHeight);  // 渲染完成
}, 0);

二、什么是 Promise

Promise 是 JavaScript 中用于处理 异步操作 的对象,它代表一个 未来才会完成(或失败)的事件,并提供统一的 API 来处理结果。

为什么需要 Promise?

在 Promise 出现之前,异步编程主要靠回调函数,容易陷入"回调地狱"(Callback Hell):

// ❌ 回调地狱:层层嵌套,难以维护
getUser(id, function (user) {
  getOrders(user, function (orders) {
    getDetails(orders[0], function (detail) {
      // 嵌套继续...
    });
  });
});

// ✅ Promise:链式调用,扁平清晰
getUser(id)
  .then(user => getOrders(user))       // 上一步的结果传给下一步
  .then(orders => getDetails(orders[0]))
  .then(detail => console.log(detail))
  .catch(err => console.error(err));   // 统一捕获错误

Promise 的核心特点

特点 说明
状态不可逆 一旦从 pending 变为 fulfilled 或 rejected,就永远固定
链式调用 .then() 返回新 Promise,可以无限链下去
值穿透 未处理的值/错误会自动传递到下一个 then/catch
微任务 Promise 回调属于 微任务(microtask),优先级高于宏任务

三、Promise 的三种状态

Promise 有且仅有三种状态:

  • pending(进行中):初始状态,既没成功也没失败
  • fulfilled(已成功):异步操作成功完成
  • rejected(已失败):异步操作失败

状态流转规则

// 状态只能从 pending → fulfilled 或 pending → rejected
// 一旦改变,就「凝固」住,不会再变
const p = new Promise((resolve, reject) => {
  resolve('成功');   // 状态:pending → fulfilled
  reject('失败');    // ⚠️ 无效!状态已凝固,不会变成 rejected
  resolve('再次');   // ⚠️ 无效!只有第一次 resolve 生效
});

四、构造函数与参数详解

基本语法

const promise = new Promise((resolve, reject) => {
  // executor 函数体(执行器)
  // 这里通常放异步操作
});

参数说明

Promise 构造函数接收一个执行器函数 executor,该函数有 两个参数:

参数 类型 作用
resolve Function 调用后将 Promise 状态变为 fulfilled,传入的值作为成功结果
reject Function 调用后将 Promise 状态变为 rejected,传入的值作为失败原因

完整示例

// 模拟一个网络请求
function fetchData(url) {
  // 返回一个新的 Promise 实例
  return new Promise((resolve, reject) => {
    // executor 是【同步执行】的(这点很重要!)
    console.log('executor 立即执行');

    // 模拟异步操作
    setTimeout(() => {
      const success = Math.random() > 0.5;        // 50% 概率成功
      if (success) {
        resolve({ data: 'hello', url });          // 把结果传出去
      } else {
        reject(new Error('请求失败'));            // 把错误传出去(推荐传 Error 对象)
      }
    }, 1000);
  });
}

// 使用
fetchData('/api/user')
  .then(res => console.log('成功:', res))
  .catch(err => console.log('失败:', err.message));

⚠️ 注意: executor 函数是同步执行的,但 resolve/reject 通常在异步回调中调用。


五、Promise 实例方法

5.1 .then(onFulfilled, onRejected)

注册成功/失败回调,返回一个新的 Promise,因此可以链式调用。

Promise.resolve(1)
  .then(value => {
    console.log(value);          // 1
    return value + 1;            // 返回值会作为下一个 then 的参数
  })
  .then(value => {
    console.log(value);          // 2
    return Promise.resolve(value + 10);  // 也可以返回 Promise
  })
  .then(value => {
    console.log(value);          // 12(自动等待 Promise 解析)
  });

.then() 返回值规则:

回调返回 新 Promise 的状态
普通值 x fulfilled,值为 x
抛出错误 rejected,原因为该错误
一个 Promise 新 Promise 跟随该 Promise 的状态
undefined(无 return) fulfilled,值为 undefined

5.2 .catch(onRejected)

捕获错误,等价于 .then(undefined, onRejected),但更推荐使用 .catch()。

Promise.reject(new Error('出错了'))
  .then(val => console.log(val))   // 不会执行
  .catch(err => {
    console.log(err.message);      // '出错了'
  });

// 链式调用中,前面任何环节的错误都会冒泡到最近的 catch
fetchUser()
  .then(user => fetchOrders(user))
  .then(orders => render(orders))
  .catch(err => console.error('任何环节出错都在这里捕获', err));

5.3 .finally(onFinally)

无论成功失败都会执行,常用于清理工作(如关闭 loading)。

showLoading();                     // 显示加载动画
fetchData()
  .then(data => render(data))
  .catch(err => showError(err))
  .finally(() => {
    hideLoading();                 // ✅ 不管成败都关闭加载
  });

注意: finally 的回调不接收任何参数,因为它无法判断是 resolve 还是 reject 的。


六、Promise 静态方法

6.1 Promise.resolve(value) / Promise.reject(reason)

快速创建一个已完成/已失败的 Promise。

Promise.resolve(42).then(v => console.log(v));  // 42
Promise.reject('err').catch(e => console.log(e)); // 'err'

// 如果传入的是 Promise,会原样返回(不会包两层)
const p = Promise.resolve(123);
console.log(Promise.resolve(p) === p);  // true

6.2 Promise.all(iterable) —— 全部成功

并行执行多个 Promise,全部成功才成功;任一失败就立即失败。

const p1 = fetch('/api/user');
const p2 = fetch('/api/orders');
const p3 = fetch('/api/products');

Promise.all([p1, p2, p3])
  .then(([user, orders, products]) => {
    // 三个都成功才进来,结果按【输入顺序】返回
    console.log(user, orders, products);
  })
  .catch(err => {
    // 任何一个失败就立即进 catch(其他请求不会被取消!)
    console.error('有一个挂了:', err);
  });

6.3 Promise.allSettled(iterable) —— 全部完成

等待所有 Promise 完成(不管成败),返回每个的最终状态。

Promise.allSettled([
  Promise.resolve(1),
  Promise.reject('err'),
  Promise.resolve(3)
]).then(results => {
  // results 一定是数组,每项格式如下:
  // { status: 'fulfilled', value: 1 }
  // { status: 'rejected', reason: 'err' }
  // { status: 'fulfilled', value: 3 }
  results.forEach(r => {
    if (r.status === 'fulfilled') console.log('✅', r.value);
    else console.log('❌', r.reason);
  });
});

6.4 Promise.race(iterable) —— 竞速

谁最先完成(成功或失败)就用谁的结果,常用于 超时控制。

// 给请求加 5 秒超时
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('超时')), ms)
  );
  return Promise.race([promise, timeout]);  // 谁先完成用谁的
}

withTimeout(fetch('/api/slow'), 5000)
  .then(res => console.log(res))
  .catch(err => console.log(err.message)); // '超时' 或网络错误

6.5 Promise.any(iterable) —— 任一成功

只要有任意一个成功就返回该结果;全部失败才返回 AggregateError。

Promise.any([
  Promise.reject('err1'),
  Promise.resolve('🎉'),
  Promise.reject('err2')
]).then(value => {
  console.log(value);  // '🎉' —— 第一个成功的
});

Promise.any([
  Promise.reject('e1'),
  Promise.reject('e2')
]).catch(err => {
  console.log(err.errors);  // ['e1', 'e2']  —— AggregateError
});

静态方法对比表

方法 何时成功 何时失败 返回值
Promise.all 全部成功 任一失败 成功值数组 / 第一个失败原因
Promise.allSettled 总是完成 永不失败 状态对象数组
Promise.race 第一个完成的(不论成败) 同左 第一个完成的结果
Promise.any 任一成功 全部失败 第一个成功值 / AggregateError

七、注意点与常见坑

🕳️ 坑 1:忘记 return,链式断裂

// ❌ 错误:忘记 return,下一个 then 拿到 undefined
getUser()
  .then(user => {
    getOrders(user.id);          // 没 return!
  })
  .then(orders => {
    console.log(orders);         // ❌ undefined
  });

// ✅ 正确
getUser()
  .then(user => {
    return getOrders(user.id);   // ✅ 必须 return
  })
  .then(orders => {
    console.log(orders);         // ✅ 拿到正确数据
  });

🕳️ 坑 2:.then().catch() 顺序

// catch 只能捕获它【前面】的错误
Promise.reject('err')
  .catch(e => console.log('捕获:', e))   // ✅ 捕获到 'err'
  .then(() => console.log('继续执行'));  // ✅ 也会执行

// 而下面这种则不会捕获
Promise.reject('err')
  .then(() => console.log('不会执行'))
  .catch(e => console.log('在这里捕获'));

🕳️ 坑 3:未捕获的 rejection

// ❌ 没有 .catch,浏览器会报 UnhandledPromiseRejection
Promise.reject('err').then(v => console.log(v));

// ✅ 始终在链尾加 catch
Promise.reject('err')
  .then(v => console.log(v))
  .catch(e => console.error(e));

🕳️ 坑 4:Promise 不可取消

原生 Promise 没有取消机制,需要用 AbortController 配合:

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
  .then(res => res.json())
  .catch(err => {
    if (err.name === 'AbortError') console.log('已取消');
  });

// 取消请求
controller.abort();

🕳️ 坑 5:executor 中的同步错误会被自动捕获

new Promise((resolve, reject) => {
  throw new Error('同步抛错');     // 等价于 reject(new Error('同步抛错'))
}).catch(e => console.log(e.message));  // ✅ 能捕获

八、手写 Promise 实现

下面实现一个符合 Promise/A+ 规范 的简化版 Promise:

// 三种状态用常量表示,避免魔法字符串
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    this.status = PENDING;       // 初始状态
    this.value = undefined;       // 成功的值
    this.reason = undefined;      // 失败的原因
    this.onFulfilledCallbacks = []; // 成功回调队列(处理异步 resolve)
    this.onRejectedCallbacks = []; // 失败回调队列

    // resolve 函数:将状态由 pending 改为 fulfilled
    const resolve = (value) => {
      if (this.status === PENDING) {           // 状态不可逆,做防护
        this.status = FULFILLED;
        this.value = value;
        // 异步注册的回调,统一在这里触发
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    // reject 函数:将状态由 pending 改为 rejected
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    // executor 同步执行,并捕获其中的异常
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);                  // 抛错等价于 reject
    }
  }

  // then 是核心方法,返回新的 Promise 以支持链式调用
  then(onFulfilled, onRejected) {
    // 值穿透:如果回调不是函数,则透传
    onFulfilled = typeof onFulfilled === 'function'
      ? onFulfilled
      : (value) => value;
    onRejected = typeof onRejected === 'function'
      ? onRejected
      : (reason) => { throw reason; };

    // 必须返回新的 Promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 用 setTimeout 模拟微任务(真正的实现应使用 queueMicrotask)
      const handleFulfilled = () => {
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.value);   // 执行回调拿返回值
            resolvePromise(promise2, x, resolve, reject); // 处理返回值
          } catch (err) {
            reject(err);
          }
        });
      };

      const handleRejected = () => {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      };

      // 根据当前状态决定执行还是入队
      if (this.status === FULFILLED) {
        handleFulfilled();
      } else if (this.status === REJECTED) {
        handleRejected();
      } else {
        // pending 时把回调存起来,等 resolve/reject 触发
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });

    return promise2;
  }

  // catch 是 then 的语法糖
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  // finally 实现:无论成败都执行,并保持原始结果穿透
  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),
      reason => MyPromise.resolve(callback()).then(() => { throw reason; })
    );
  }

  // 静态方法:resolve
  static resolve(value) {
    if (value instanceof MyPromise) return value;   // 是 Promise 直接返回
    return new MyPromise(resolve => resolve(value));
  }

  // 静态方法:reject
  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }

  // 静态方法:all
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;                                 // 已完成数量
      promises.forEach((p, i) => {
        MyPromise.resolve(p).then(
          value => {
            results[i] = value;                      // 按【索引】放结果,保证顺序
            count++;
            if (count === promises.length) resolve(results);
          },
          reject                                     // 任一失败立即 reject
        );
      });
    });
  }

  // 静态方法:race
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      // 谁先完成(不管成败)就调谁
      promises.forEach(p => MyPromise.resolve(p).then(resolve, reject));
    });
  }
}

// 处理 then 回调返回值的工具函数(Promise/A+ 关键逻辑)
function resolvePromise(promise2, x, resolve, reject) {
  // 防止循环引用:return 自己导致死循环
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }
  // 如果 x 是 Promise(thenable),跟随它的状态
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    // 普通值,直接 resolve
    resolve(x);
  }
}

// ===== 测试 =====
new MyPromise((resolve) => {
  setTimeout(() => resolve('🎉 success'), 1000);
})
  .then(v => {
    console.log(v);              // 🎉 success
    return v + ' → step2';
  })
  .then(v => console.log(v));    // 🎉 success → step2

关键点: 真正的 Promise/A+ 还需要处理 thenable 对象、严格的微任务调度等,上面是简化版便于理解核心思路。


九、async / await

async/await 是 ES2017 引入的 Promise 的语法糖,让异步代码看起来像同步代码。

9.1 基本用法

// async 关键字声明的函数总是返回一个 Promise
async function getData() {
  return 42;                  // 等价于 return Promise.resolve(42)
}

getData().then(v => console.log(v));  // 42

// await 只能在 async 函数内(或顶层模块)使用
async function main() {
  const user = await fetchUser();         // 等待 Promise 解析,拿到值
  const orders = await fetchOrders(user); // 串行执行
  console.log(orders);
}

9.2 错误处理

用 try / catch 替代 .catch(),更直观:

async function loadProfile() {
  try {
    const user = await fetchUser();      // 任一步出错都会跳到 catch
    const detail = await fetchDetail(user.id);
    return detail;
  } catch (err) {
    console.error('加载失败:', err);
    throw err;                            // 如需要可以再抛
  } finally {
    hideLoading();                        // 总会执行
  }
}

9.3 并行优化

⚠️ 常见性能坑: 不必要的串行 await。

// ❌ 串行:总耗时 = a + b + c
async function bad() {
  const a = await taskA();     // 等 1s
  const b = await taskB();     // 又等 1s
  const c = await taskC();     // 再等 1s(共 3s)
  return [a, b, c];
}

// ✅ 并行:总耗时 = max(a, b, c)
async function good() {
  // 先发起所有请求,再一起 await
  const [a, b, c] = await Promise.all([taskA(), taskB(), taskC()]);
  return [a, b, c];            // 共 1s
}

9.4 循环中的 await

const ids = [1, 2, 3];

// ❌ forEach 不支持 async(await 不会生效)
ids.forEach(async id => {
  await fetchUser(id);          // 不会等待,全部并发
});

// ✅ 串行:用 for...of
async function serial() {
  for (const id of ids) {
    const user = await fetchUser(id);  // 一个一个来
    console.log(user);
  }
}

// ✅ 并行:用 Promise.all + map
async function parallel() {
  const users = await Promise.all(ids.map(id => fetchUser(id)));
  console.log(users);
}

9.5 顶层 await

ES2022 起,ES Module 中可直接在顶层使用 await:

// module.mjs(必须是 ES Module)
const data = await fetch('/api/config').then(r => r.json());
export default data;

十、对比总结

Promise vs async/await

特性 Promise (.then) async/await
语法风格 链式调用 同步风格
错误处理 .catch() try/catch
可读性 嵌套较深时差 更接近自然思维
调试 异步栈较难追踪 断点友好
并行 Promise.all 仍需配合 Promise.all
底层 原生对象 基于 Promise 的语法糖

选用建议

  • 简单的单步异步 → async/await 更清晰
  • 多个并行任务 → await Promise.all([...]) 组合
  • 需要竞速/超时 → Promise.race
  • 容错聚合 → Promise.allSettled
  • 写库给别人用 → 返回 Promise,让用户自己选 then 或 await