为什么异步编程是前端核心能力
JavaScript 是单线程语言,所有 I/O 操作(网络请求、文件读取、定时器)都必须异步完成。异步编程能力直接决定了你能否写出高性能、无阻塞的应用代码。几乎每场前端面试都会涉及 Promise 和 async/await。
一、异步编程演进史
1.1 回调函数(Callback)
最原始的异步模式:
fs.readFile('a.txt', (err, dataA) => {
if (err) throw err;
fs.readFile('b.txt', (err, dataB) => {
if (err) throw err;
fs.readFile('c.txt', (err, dataC) => {
// Callback Hell / Pyramid of Doom
});
});
});
问题:嵌套层级深(回调地狱)、错误处理分散、控制反转(Inversion of Control)——你把回调交给第三方,无法保证它被正确调用。
1.2 Promise(ES2015)
Promise 用状态机模型解决了回调的核心问题。
1.3 async/await(ES2017)
在 Promise 之上提供了同步风格的语法,是目前最主流的异步编程范式。
二、Promise 深入
2.1 三种状态
pending ──resolve(value)──→ fulfilled
│
└──reject(reason)──→ rejected
- 状态只能从
pending转为fulfilled或rejected - 一旦状态确定(settled),永远不可逆
resolve(anotherPromise)会"锁定"到另一个 Promise 的状态(Resolution Procedure)
2.2 基本用法
const p = new Promise((resolve, reject) => {
// executor 立即同步执行
asyncOperation((err, data) => {
if (err) reject(err);
else resolve(data);
});
});
p.then(onFulfilled, onRejected);
2.3 链式调用(Chaining)
.then() 总是返回一个新的 Promise,这是链式调用的基础:
fetch('/api/user')
.then(res => res.json()) // 返回新 Promise
.then(user => fetch(`/api/posts/${user.id}`))
.then(res => res.json())
.then(posts => console.log(posts))
.catch(err => console.error(err)) // 捕获链中任何一步的错误
.finally(() => hideLoading()); // 无论成功失败都执行
链式调用的值传递规则:
.then(handler) 中 handler 的返回值 |
下一个 .then 接收到的值 |
|---|---|
普通值 x |
x |
Promise.resolve(x) |
x(等待 resolve) |
Promise.reject(err) |
进入下一个 .catch |
抛出异常 throw err |
进入下一个 .catch |
| 什么都不返回 | undefined |
2.4 Promise Resolution Procedure
当 resolve(value) 被调用时,如果 value 是一个 thenable(具有 .then 方法的对象),Promise 会"展开"它:
const p = new Promise(resolve => {
resolve(Promise.resolve(42));
});
p.then(val => console.log(val)); // 42(不是 Promise 对象)
这就是 Promises/A+ 规范中定义的 Resolution Procedure,保证了 Promise 的可互操作性。
2.5 错误处理
// ✅ 推荐:在链末尾统一 catch
fetchData()
.then(process)
.then(save)
.catch(handleError);
// ❌ 避免:在每个 then 中处理错误
fetchData()
.then(process, handleError1) // handleError1 无法捕获 process 中的错误
.then(save, handleError2);
未处理的 rejection:
// 浏览器和 Node.js 都会触发 unhandledrejection 事件
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason);
});
三、Promise 静态方法
| 方法 | 行为 | 典型场景 |
|---|---|---|
Promise.all(iterable) |
全部 fulfilled → fulfilled;任一 reject → reject | 并行请求,全部成功才继续 |
Promise.allSettled(iterable) |
等待全部 settled | 并行请求,不关心个别失败 |
Promise.race(iterable) |
第一个 settled 的结果 | 超时竞赛 |
Promise.any(iterable) |
第一个 fulfilled 的结果;全部 reject → AggregateError | 最快成功的结果 |
Promise.resolve(value) |
包装为 fulfilled Promise | 统一同步/异步接口 |
Promise.reject(reason) |
包装为 rejected Promise | 立即拒绝 |
Promise.withResolvers() |
返回 { promise, resolve, reject } |
替代 Deferred 模式(ES2024) |
经典场景:请求超时
function fetchWithTimeout(url, ms) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
]);
}
并发控制
// 限制并发数的 Promise 调度器
class Scheduler {
#max;
#running = 0;
#queue = [];
constructor(max) { this.#max = max; }
add(promiseCreator) {
return new Promise(resolve => {
this.#queue.push(() => promiseCreator().then(resolve));
this.#run();
});
}
#run() {
while (this.#running < this.#max && this.#queue.length) {
const task = this.#queue.shift();
this.#running++;
task().finally(() => {
this.#running--;
this.#run();
});
}
}
}
四、async/await 深入
4.1 本质:Generator + Promise 的语法糖
async 函数实际上是返回 Promise 的函数,await 是暂停执行等待 Promise resolve 的语法:
async function fetchUser() {
const res = await fetch('/api/user'); // 暂停,等待 fetch resolve
const user = await res.json(); // 暂停,等待 json() resolve
return user; // 等价于 resolve(user)
}
// 等价的 Promise 写法
function fetchUser() {
return fetch('/api/user')
.then(res => res.json());
}
4.2 编译语义
从概念上,async/await 可以理解为 Generator 的自动执行:
// async/await
async function foo() {
const a = await promise1;
const b = await promise2;
return a + b;
}
// 概念上等价于
function foo() {
return spawn(function* () {
const a = yield promise1;
const b = yield promise2;
return a + b;
});
}
// spawn 是自动执行器(类似 co 库)
function spawn(genFn) {
return new Promise((resolve, reject) => {
const gen = genFn();
function step(method, value) {
try {
const { done, value: next } = gen[method](value);
if (done) resolve(next);
else Promise.resolve(next).then(
val => step('next', val),
err => step('throw', err)
);
} catch (e) { reject(e); }
}
step('next', undefined);
});
}
4.3 错误处理
// 方式一:try/catch(最常用)
async function load() {
try {
const data = await fetchData();
return process(data);
} catch (err) {
console.error('Failed:', err);
}
}
// 方式二:.catch() 在调用处处理
load().catch(handleError);
// 方式三:Go 风格的 tuple 解构
async function to(promise) {
try {
return [null, await promise];
} catch (err) {
return [err, null];
}
}
const [err, data] = await to(fetchData());
if (err) handleError(err);
4.4 常见陷阱
串行 vs 并行:
// ❌ 串行执行 — 总耗时 = t1 + t2
async function serial() {
const a = await fetch('/api/a'); // 等待完成后
const b = await fetch('/api/b'); // 才发起第二个请求
}
// ✅ 并行执行 — 总耗时 = max(t1, t2)
async function parallel() {
const [a, b] = await Promise.all([
fetch('/api/a'),
fetch('/api/b')
]);
}
forEach 中的 await 不生效:
// ❌ forEach 不会等待 async 回调
urls.forEach(async (url) => {
await fetch(url); // 所有请求同时发出,不会按顺序
});
// ✅ 顺序执行用 for...of
for (const url of urls) {
await fetch(url);
}
// ✅ 并行执行用 Promise.all + map
await Promise.all(urls.map(url => fetch(url)));
五、微任务与执行顺序
Promise.then/catch/finally 的回调以及 await 之后的代码都是作为 微任务(microtask) 执行的。这意味着它们在当前同步代码执行完毕后、下一个宏任务之前执行。
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1, 4, 3, 2
详细的微任务/宏任务调度机制,参见 event-loop 专题。
六、面试高频题型
题型 1:Promise 输出顺序
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(4);
})
.then(res => console.log(res));
Promise.resolve()
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3));
// 输出:0, 1, 2, 3, 4
// 解释:return Promise.resolve(4) 需要额外两个微任务来"展开"
题型 2:实现 Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let settled = 0;
const arr = Array.from(promises);
if (arr.length === 0) return resolve([]);
arr.forEach((p, i) => {
Promise.resolve(p).then(
value => {
results[i] = value;
if (++settled === arr.length) resolve(results);
},
reject // 任一失败立即 reject
);
});
});
}
题型 3:实现带并发限制的批量请求
见第三节中的 Scheduler 实现。
题型 4:async 函数的返回值
async function foo() { return 1; }
async function bar() { return Promise.resolve(2); }
foo().then(console.log); // 1
bar().then(console.log); // 2(自动展开)
// async 函数总是返回 Promise
typeof foo(); // 'object'(Promise 实例)
七、手写 Promise(简化版)
class MyPromise {
#state = 'pending';
#value = undefined;
#callbacks = [];
constructor(executor) {
const resolve = (value) => this.#transition('fulfilled', value);
const reject = (reason) => this.#transition('rejected', reason);
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
#transition(state, value) {
if (this.#state !== 'pending') return;
this.#state = state;
this.#value = value;
queueMicrotask(() => this.#callbacks.forEach(cb => cb()));
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = () => {
const handler = this.#state === 'fulfilled' ? onFulfilled : onRejected;
const settle = this.#state === 'fulfilled' ? resolve : reject;
if (typeof handler !== 'function') {
settle(this.#value);
return;
}
try {
const result = handler(this.#value);
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
};
if (this.#state === 'pending') {
this.#callbacks.push(handle);
} else {
queueMicrotask(handle);
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
value => MyPromise.resolve(onFinally()).then(() => value),
reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
}
八、与其他主题的关联
| 关联主题 | 关系 |
|---|---|
| event-loop | Promise 回调在微任务队列中执行,理解事件循环才能预测执行顺序 |
| error-handling | Promise rejection 的传播机制、unhandledrejection |
| iterators-generators | async/await 的底层机制基于 Generator |
| scope-closure | async 函数中的 stale closure 问题 |
参考资料
- Promises/A+ 规范 — promisesaplus.com — Promise 互操作性的基准规范
- MDN Web Docs — Promise
- MDN Web Docs — async function
- Jake Archibald — JavaScript Promises: An Introduction — 图文并茂的入门
- ECMA-262 — Promise Objects
- Dr. Axel Rauschmayer — JavaScript for impatient programmers — async 章节