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、4 setTimeout回调放入宏任务队列Promise.then回调放入微任务队列- 同步代码执行完,检查微任务队列,执行输出
3 - 微任务清空,执行下一个宏任务,输出
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