异步编程
1. JavaScript 异步模型
1.1 为什么需要异步?
1.2 异步编程演进
2. Event Loop (事件循环) 🔥
2.1 执行机制
2.2 宏任务 vs 微任务
| 类型 | 任务来源 |
|---|---|
| 宏任务 | setTimeout, setInterval, setImmediate(Node), I/O, UI事件 |
| 微任务 | Promise.then/catch/finally, queueMicrotask, MutationObserver, process.nextTick(Node) |
2.3 执行顺序
javascript
console.log('1'); // 同步
setTimeout(() => console.log('2'), 0); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4'); // 同步
// 输出: 1, 4, 3, 22.4 经典面试题
javascript
console.log('start');
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
setTimeout(() => console.log('timeout inside promise'), 0);
});
Promise.resolve().then(() => console.log('promise2'));
console.log('end');
// 输出顺序:
// start
// end
// promise1
// promise2
// timeout1
// promise inside timeout
// timeout inside promise2.5 Node.js Event Loop
javascript
// Node.js 特有行为
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
// 输出顺序不确定! (取决于进入 event loop 的时机)
// 但在 I/O 回调中, setImmediate 总是先执行
const fs = require('fs');
fs.readFile('file.txt', () => {
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
});
// 输出: immediate, timeout (确定)3. Callback (回调函数)
3.1 基本模式
javascript
function fetchData(callback) {
setTimeout(() => {
callback(null, { data: 'Hello' });
}, 1000);
}
fetchData((err, result) => {
if (err) {
console.error(err);
return;
}
console.log(result);
});3.2 回调地狱 (Callback Hell)
javascript
// ❌ 问题: 嵌套过深, 难以维护
getUser(userId, (err, user) => {
if (err) return handleError(err);
getOrders(user.id, (err, orders) => {
if (err) return handleError(err);
getProducts(orders[0].id, (err, products) => {
if (err) return handleError(err);
getDetails(products[0].id, (err, details) => {
if (err) return handleError(err);
console.log(details);
});
});
});
});3.3 Node.js 错误优先回调
javascript
// 约定: 第一个参数是错误对象
fs.readFile('file.txt', (err, data) => {
if (err) {
console.error('Error:', err);
return;
}
console.log(data);
});4. Promise 🔥
4.1 基本用法
javascript
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('成功');
} else {
reject(new Error('失败'));
}
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('完成'));4.2 Promise 状态
4.3 链式调用
javascript
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/orders/${user.id}`))
.then(response => response.json())
.then(orders => console.log(orders))
.catch(error => console.error(error));
// 每个 then 返回新的 Promise
// then 回调返回值会被 Promise.resolve() 包装4.4 静态方法
javascript
// Promise.all - 全部成功才成功
const results = await Promise.all([
fetch('/api/users'),
fetch('/api/products'),
fetch('/api/orders')
]);
// 任一失败则整个失败
// Promise.allSettled - 等待全部完成
const results = await Promise.allSettled([
fetch('/api/users'),
Promise.reject('error')
]);
// [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: 'error' }]
// Promise.race - 返回最先完成的
const result = await Promise.race([
fetch('/api/users'),
new Promise((_, reject) => setTimeout(() => reject('timeout'), 5000))
]);
// Promise.any - 返回第一个成功的 (ES2021)
const result = await Promise.any([
fetch('/api/server1'),
fetch('/api/server2'),
fetch('/api/server3')
]);
// 全部失败才失败 (AggregateError)
// Promise.resolve / Promise.reject
Promise.resolve('value'); // 立即 fulfilled
Promise.reject(new Error('error')); // 立即 rejected4.5 错误处理
javascript
// ❌ 错误: then 的第二个参数无法捕获 then 回调中的错误
promise.then(
result => { throw new Error('oops'); },
error => { /* 捕获不到上面的错误 */ }
);
// ✅ 正确: 使用 catch
promise
.then(result => { throw new Error('oops'); })
.catch(error => console.error(error)); // 可以捕获
// 未处理的 rejection
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled:', event.reason);
event.preventDefault();
});4.6 手写 Promise
javascript
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const handleRejected = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.status === MyPromise.FULFILLED) {
handleFulfilled();
} else if (this.status === MyPromise.REJECTED) {
handleRejected();
} else {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).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));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
if (++count === promises.length) {
resolve(results);
}
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
}5. Generator (生成器)
5.1 基本语法
javascript
function* generator() {
console.log('start');
const a = yield 1;
console.log('a:', a);
const b = yield 2;
console.log('b:', b);
return 3;
}
const gen = generator();
console.log(gen.next()); // start → { value: 1, done: false }
console.log(gen.next('A')); // a: A → { value: 2, done: false }
console.log(gen.next('B')); // b: B → { value: 3, done: true }
console.log(gen.next()); // → { value: undefined, done: true }5.2 Generator 实现异步
javascript
function* fetchUser() {
const user = yield fetch('/api/user');
const orders = yield fetch(`/api/orders/${user.id}`);
return orders;
}
// 手动执行器
function run(generator) {
const gen = generator();
function step(value) {
const result = gen.next(value);
if (result.done) {
return Promise.resolve(result.value);
}
return Promise.resolve(result.value)
.then(res => res.json())
.then(data => step(data));
}
return step();
}
run(fetchUser).then(orders => console.log(orders));5.3 co 库 (自动执行器)
javascript
const co = require('co');
co(function* () {
const user = yield fetch('/api/user').then(r => r.json());
const orders = yield fetch(`/api/orders/${user.id}`).then(r => r.json());
return orders;
}).then(orders => console.log(orders));6. Async/Await 🔥
6.1 基本语法
javascript
// async 函数总是返回 Promise
async function fetchData() {
// await 暂停执行, 等待 Promise resolve
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
// 等价于
function fetchData() {
return fetch('/api/data')
.then(response => response.json());
}6.2 错误处理
javascript
// 方式1: try...catch
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error:', error);
throw error; // 重新抛出或返回默认值
}
}
// 方式2: 包装函数
function to(promise) {
return promise
.then(data => [null, data])
.catch(err => [err, null]);
}
async function fetchData() {
const [err, response] = await to(fetch('/api/data'));
if (err) return console.error(err);
const [err2, data] = await to(response.json());
if (err2) return console.error(err2);
return data;
}6.3 并行执行
javascript
// ❌ 串行执行 (慢)
async function serial() {
const user = await fetchUser(); // 等待 1 秒
const orders = await fetchOrders(); // 再等待 1 秒
// 总共 2 秒
}
// ✅ 并行执行 (快)
async function parallel() {
const [user, orders] = await Promise.all([
fetchUser(),
fetchOrders()
]);
// 总共 1 秒 (取决于最慢的那个)
}
// 并行发起, 按序等待
async function parallelStart() {
const userPromise = fetchUser();
const ordersPromise = fetchOrders();
const user = await userPromise;
const orders = await ordersPromise;
}6.4 循环中的 async/await
javascript
const urls = ['/api/1', '/api/2', '/api/3'];
// ❌ forEach 无法等待
urls.forEach(async url => {
const data = await fetch(url); // 并行执行, 但无法等待全部完成
});
// ✅ for...of 串行执行
async function serial() {
for (const url of urls) {
const data = await fetch(url); // 一个完成再执行下一个
console.log(data);
}
}
// ✅ Promise.all 并行执行
async function parallel() {
const results = await Promise.all(
urls.map(url => fetch(url))
);
console.log(results);
}
// ✅ for await...of (异步迭代)
async function* asyncGenerator() {
for (const url of urls) {
yield fetch(url);
}
}
for await (const response of asyncGenerator()) {
console.log(response);
}6.5 顶层 await (ES2022)
javascript
// 在模块顶层直接使用 await
// file.mjs
const data = await fetch('/api/config').then(r => r.json());
export { data };
// 注意: 仅在 ES Module 中可用7. 异步控制流
7.1 并发限制
javascript
async function asyncPool(limit, items, iteratorFn) {
const results = [];
const executing = [];
for (const [index, item] of items.entries()) {
const p = Promise.resolve().then(() => iteratorFn(item, index));
results.push(p);
if (limit <= items.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// 使用: 最多同时 3 个请求
const urls = [url1, url2, url3, url4, url5, url6, url7, url8, url9, url10];
await asyncPool(3, urls, async (url) => {
const res = await fetch(url);
return res.json();
});7.2 重试机制
javascript
async function retry(fn, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`Retry ${i + 1}/${retries}...`);
await new Promise(r => setTimeout(r, delay * Math.pow(2, i))); // 指数退避
}
}
}
// 使用
const data = await retry(() => fetch('/api/unstable'), 3, 1000);7.3 超时控制
javascript
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
return Promise.race([promise, timeout]);
}
// 使用
try {
const data = await withTimeout(fetch('/api/slow'), 5000);
} catch (error) {
if (error.message === 'Timeout') {
console.log('Request timed out');
}
}
// 现代方式: AbortSignal.timeout
const response = await fetch('/api/slow', {
signal: AbortSignal.timeout(5000)
});7.4 取消异步操作
javascript
// AbortController
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
}
});
// 取消
controller.abort();
// 可取消的 Promise 包装
function cancellable(executor) {
let cancel;
const promise = new Promise((resolve, reject) => {
cancel = () => reject(new Error('Cancelled'));
executor(resolve, reject);
});
return { promise, cancel };
}7.5 防抖与节流
javascript
// 防抖: 延迟执行, 期间有新调用则重新计时
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 异步防抖
function asyncDebounce(fn, delay) {
let timer;
let resolveList = [];
return function (...args) {
return new Promise((resolve) => {
clearTimeout(timer);
resolveList.push(resolve);
timer = setTimeout(async () => {
const result = await fn.apply(this, args);
resolveList.forEach(r => r(result));
resolveList = [];
}, delay);
});
};
}
// 节流: 固定间隔执行
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
return fn.apply(this, args);
}
};
}8. 面试高频问题
Q1: 宏任务和微任务的区别?
| 维度 | 宏任务 | 微任务 |
|---|---|---|
| 代表 | setTimeout, I/O | Promise.then, queueMicrotask |
| 执行时机 | 每次取一个执行 | 清空所有微任务后再取宏任务 |
| 优先级 | 低 | 高 |
Q2: Promise.all 和 Promise.allSettled 的区别?
Promise.all: 全部成功才成功,任一失败则立即失败Promise.allSettled: 等待全部完成,返回每个结果的状态
Q3: async/await 的本质是什么?
async/await 是 Generator 的语法糖,async 函数返回 Promise,await 相当于 yield,自动执行器处理 next() 调用。
Q4: 如何处理多个 async 操作的并行执行?
使用 Promise.all:
javascript
const [a, b, c] = await Promise.all([fnA(), fnB(), fnC()]);Q5: setTimeout 的最小延迟是多少?
- 浏览器: 嵌套超过 5 层后最小 4ms
- Node.js: 最小 1ms
- HTML5 规范: 最小 4ms
Q6: process.nextTick 和 Promise.then 哪个先执行?
Node.js 中 process.nextTick 优先级高于 Promise.then 微任务。
javascript
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// 输出: nextTick, promiseQ7: 如何取消一个 Promise?
Promise 本身不可取消,但可以:
- 使用
AbortController(fetch) - 使用取消令牌模式
- 使用
Promise.race与超时 Promise
Q8: async 函数中 return 和 return await 有区别吗?
在 try...catch 中有区别:
javascript
async function foo() {
try {
return Promise.reject('error'); // 不会被 catch
// return await Promise.reject('error'); // 会被 catch
} catch (e) {
console.log('caught:', e);
}
}Q9: 如何实现并发限制?
使用 asyncPool 模式:
- 维护正在执行的 Promise 数组
- 当达到限制时使用
Promise.race等待 - 完成一个后添加下一个
Q10: Event Loop 在浏览器和 Node.js 中有什么区别?
| 维度 | 浏览器 | Node.js |
|---|---|---|
| 微任务时机 | 每个宏任务后执行 | 每个阶段切换时执行 |
| 特有 API | requestAnimationFrame | setImmediate, process.nextTick |
| 阶段 | 简单循环 | 6 个阶段 |