跳到主要内容

异步编程

问题

JavaScript 异步编程有哪些方式?Promise 和 async/await 如何使用?

答案

JavaScript 是单线程语言,使用异步编程来处理耗时操作,避免阻塞主线程。异步编程经历了从回调函数Promise 再到 async/await 的演进。

异步编程演进

回调函数

// 回调地狱示例
function getUserData(userId: string, callback: (user: any) => void): void {
setTimeout(() => {
callback({ id: userId, name: 'Alice' });
}, 100);
}

function getOrders(userId: string, callback: (orders: any[]) => void): void {
setTimeout(() => {
callback([{ id: 1, product: 'Phone' }]);
}, 100);
}

function getOrderDetails(orderId: number, callback: (details: any) => void): void {
setTimeout(() => {
callback({ id: orderId, price: 999 });
}, 100);
}

// ❌ 回调地狱 - 难以阅读和维护
getUserData('1', (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
console.log(details);
// 更多嵌套...
});
});
});

Promise

基本用法

// 创建 Promise
const promise = new Promise<string>((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('成功');
} else {
reject(new Error('失败'));
}
}, 100);
});

// 使用 Promise
promise
.then((value) => console.log(value))
.catch((error) => console.error(error))
.finally(() => console.log('完成'));

Promise 状态

链式调用

function fetchUser(id: string): Promise<{ id: string; name: string }> {
return new Promise((resolve) => {
setTimeout(() => resolve({ id, name: 'Alice' }), 100);
});
}

function fetchOrders(userId: string): Promise<{ id: number; product: string }[]> {
return new Promise((resolve) => {
setTimeout(() => resolve([{ id: 1, product: 'Phone' }]), 100);
});
}

// ✅ 链式调用 - 扁平化
fetchUser('1')
.then((user) => {
console.log('用户:', user);
return fetchOrders(user.id);
})
.then((orders) => {
console.log('订单:', orders);
return orders[0];
})
.then((order) => {
console.log('第一个订单:', order);
})
.catch((error) => {
console.error('错误:', error);
});

Promise 静态方法

// Promise.all - 全部成功才成功
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
];

Promise.all(promises)
.then((values) => console.log(values)) // [1, 2, 3]
.catch((error) => console.error(error)); // 任一失败则失败

// Promise.allSettled - 等待全部完成(不管成功失败)
Promise.allSettled([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3),
]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
});

// Promise.race - 第一个完成的结果
Promise.race([
new Promise(resolve => setTimeout(() => resolve('slow'), 200)),
new Promise(resolve => setTimeout(() => resolve('fast'), 100)),
]).then((value) => console.log(value)); // 'fast'

// Promise.any - 第一个成功的结果
Promise.any([
Promise.reject('error1'),
new Promise(resolve => setTimeout(() => resolve('success'), 100)),
Promise.reject('error2'),
]).then((value) => console.log(value)); // 'success'

错误处理

// 方式 1: catch 方法
fetchData()
.then(processData)
.catch((error) => {
console.error('处理错误:', error);
});

// 方式 2: then 的第二个参数
fetchData()
.then(
(data) => processData(data),
(error) => console.error(error)
);

// 建议使用 catch - 可以捕获整个链的错误
fetchData()
.then(processData)
.then(saveData)
.catch((error) => {
// 可以捕获 fetchData、processData、saveData 任一环节的错误
console.error(error);
});

async/await

基本用法

async function fetchUserData(userId: string): Promise<{ user: any; orders: any[] }> {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
return { user, orders };
} catch (error) {
console.error('获取数据失败:', error);
throw error;
}
}

// 调用
const data = await fetchUserData('1');
console.log(data);

async/await 特点

// 1. async 函数返回 Promise
async function foo(): Promise<number> {
return 1;
}
// 等价于
function foo(): Promise<number> {
return Promise.resolve(1);
}

// 2. await 暂停执行,等待 Promise 解决
async function bar(): Promise<void> {
console.log(1);
await Promise.resolve();
console.log(2); // 微任务
}
bar();
console.log(3);
// 输出: 1, 3, 2

// 3. await 可以处理非 Promise 值
async function baz(): Promise<number> {
const value = await 42; // 自动包装为 Promise.resolve(42)
return value;
}

并行执行

// ❌ 串行执行 - 慢
async function serial(): Promise<void> {
const user = await fetchUser('1'); // 100ms
const orders = await fetchOrders('1'); // 100ms
// 总共 200ms
}

// ✅ 并行执行 - 快
async function parallel(): Promise<void> {
const [user, orders] = await Promise.all([
fetchUser('1'),
fetchOrders('1'),
]);
// 总共 100ms
}

// 部分并行,部分串行
async function mixed(): Promise<void> {
// 先获取用户
const user = await fetchUser('1');

// 然后并行获取订单和收藏
const [orders, favorites] = await Promise.all([
fetchOrders(user.id),
fetchFavorites(user.id),
]);
}

错误处理

// try/catch
async function withTryCatch(): Promise<void> {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error('错误:', error);
}
}

// 统一错误处理函数
async function to<T>(
promise: Promise<T>
): Promise<[Error | null, T | null]> {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error as Error, null];
}
}

// 使用
async function example(): Promise<void> {
const [error, data] = await to(fetchData());

if (error) {
console.error('错误:', error);
return;
}

console.log('数据:', data);
}

循环中的 async/await

const ids = ['1', '2', '3'];

// ❌ forEach 不会等待
ids.forEach(async (id) => {
const user = await fetchUser(id);
console.log(user);
});
console.log('完成'); // 先输出,再输出用户

// ✅ for...of 会等待
async function sequential(): Promise<void> {
for (const id of ids) {
const user = await fetchUser(id);
console.log(user);
}
console.log('完成'); // 最后输出
}

// ✅ 并行处理
async function parallelProcess(): Promise<void> {
const users = await Promise.all(ids.map((id) => fetchUser(id)));
users.forEach((user) => console.log(user));
console.log('完成');
}

Generator 与异步

什么是 Generator

Generator(生成器)是 ES6 引入的一种可以暂停和恢复的函数。普通函数一旦执行就会一口气跑完,而 Generator 函数可以在执行过程中通过 yield 关键字暂停,之后再通过 next() 方法恢复执行

function* 声明一个 Generator 函数,调用它不会立即执行函数体,而是返回一个迭代器对象(Generator 对象),通过调用迭代器的 next() 方法来逐步执行。

基本用法

Generator 基础
function* simpleGenerator(): Generator<number, string, unknown> {
console.log('第一段代码');
yield 1; // 暂停,把 1 交给外部

console.log('第二段代码');
yield 2; // 再次暂停,把 2 交给外部

console.log('第三段代码');
return '结束'; // 函数结束
}

// 调用 Generator 函数,得到迭代器(此时函数体还没执行!)
const gen = simpleGenerator();

// 第一次 next():执行到第一个 yield,暂停
gen.next(); // 打印 "第一段代码",返回 { value: 1, done: false }

// 第二次 next():从上次暂停处继续,执行到第二个 yield
gen.next(); // 打印 "第二段代码",返回 { value: 2, done: false }

// 第三次 next():从上次暂停处继续,执行到 return
gen.next(); // 打印 "第三段代码",返回 { value: '结束', done: true }
next() 返回值说明

每次调用 next() 都返回一个对象 { value, done }

  • valueyield 后面的值(或 return 的值)
  • donefalse 表示还没结束,true 表示函数已执行完毕

next() 传值:外部向 Generator 内部传递数据

next() 方法可以接收一个参数,这个参数会作为上一个 yield 表达式的返回值。这是 Generator 最关键的特性——双向通信

next() 传值
function* adder(): Generator<number, string, number> {
const a = yield 1; // 第一次暂停。a 的值不是 1,而是下次 next() 传入的值
console.log('a =', a);

const b = yield 2; // 第二次暂停。b 的值是下次 next() 传入的值
console.log('b =', b);

return `结果: ${a + b}`;
}

const gen = adder();

// 第一次 next():执行到 yield 1,返回 { value: 1, done: false }
// ⚠️ 第一次 next() 的参数会被忽略(因为还没有"上一个 yield")
gen.next();

// 第二次 next(10):从 yield 1 处恢复,10 作为 yield 1 的返回值赋给 a
// 继续执行到 yield 2,返回 { value: 2, done: false }
gen.next(10); // 打印 "a = 10"

// 第三次 next(20):从 yield 2 处恢复,20 作为 yield 2 的返回值赋给 b
// 执行到 return,返回 { value: '结果: 30', done: true }
gen.next(20); // 打印 "b = 20"
容易混淆的点

yield 既是输出也是输入

  • 输出yield 后面的值(如 yield 1 中的 1)通过 next() 的返回值交给外部
  • 输入:下一次 next(value) 传入的 value 作为整个 yield 表达式的返回值

简单记忆:yield 送出去的值yield 接收到的值是两回事,它们通过两次不同的 next() 调用分别交换。

下面用一张时序图来帮助理解执行过程:

Generator 实现异步(async/await 的前身)

理解了 yield 的暂停/恢复和 next() 传值后,我们就能理解如何用 Generator 实现异步编程了。核心思路:

  1. yield 一个 Promise 出去(暂停函数执行)
  2. 外部等 Promise resolve 后,通过 next(result) 把结果传回来(恢复函数执行)
  3. Generator 内部拿到结果继续执行,遇到下一个 yield 再暂停
Generator + Promise 处理异步
function* fetchFlow(): Generator<Promise<any>, void, any> {
// yield 一个 Promise 出去,暂停
// 等 Promise resolve 后,外部把结果通过 next() 传回来,赋值给 user
const user = yield fetchUser('1');
console.log('用户:', user);

// 同理,yield 另一个 Promise
const orders = yield fetchOrders(user.id);
console.log('订单:', orders);
}

但 Generator 不会自动执行,需要我们写一个执行器来驱动它——不断调用 next(),等 Promise 完成后传结果回去:

自动执行器(co 函数)
function co(generatorFn: () => Generator<Promise<any>, any, any>): Promise<any> {
return new Promise((resolve, reject) => {
const gen = generatorFn();

function step(nextValue?: any): void {
let result;
try {
result = gen.next(nextValue); // 传值并恢复执行
} catch (e) {
reject(e);
return;
}

if (result.done) {
resolve(result.value); // Generator 执行完毕
return;
}

// result.value 是一个 Promise,等它 resolve 后继续
Promise.resolve(result.value)
.then(step) // 成功:把结果传给下一个 next()
.catch((e) => gen.throw(e)); // 失败:向 Generator 内部抛异常
}

step(); // 启动
});
}

// 使用执行器驱动 Generator
co(fetchFlow).then(() => console.log('全部完成'));
async/await 的本质

理解了 Generator + 执行器后,你会发现 async/await 就是这个模式的语法糖

  • async function = function* + 内置自动执行器
  • await = yield
  • 引擎帮你自动完成了 co 执行器的工作,不需要手动写
// 这两种写法在行为上是等价的:
async function fetchData() {
const user = await fetchUser('1');
const orders = await fetchOrders(user.id);
}

co(function* () {
const user = yield fetchUser('1');
const orders = yield fetchOrders(user.id);
});

异步方案对比

特性回调Promiseasync/await
代码风格嵌套链式同步
错误处理每层处理catchtry/catch
调试困难一般简单
取消困难需封装需封装
并发控制手动Promise.allPromise.all

实际应用

请求重试

async function fetchWithRetry<T>(
fn: () => Promise<T>,
retries: number = 3,
delay: number = 1000
): Promise<T> {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`重试 ${i + 1}/${retries}`);
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
throw new Error('Unreachable');
}

// 使用
const data = await fetchWithRetry(() => fetch('/api/data').then(r => r.json()));

并发限制

async function asyncPool<T, R>(
limit: number,
items: T[],
fn: (item: T) => Promise<R>
): Promise<R[]> {
const results: R[] = [];
const executing: Promise<void>[] = [];

for (const item of items) {
const p = fn(item).then((result) => {
results.push(result);
});

executing.push(p as unknown as Promise<void>);

if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(
executing.findIndex((p2) => p2 === p),
1
);
}
}

await Promise.all(executing);
return results;
}

// 使用:最多同时 3 个请求
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const results = await asyncPool(3, urls, fetchUrl);

超时处理

function withTimeout<T>(
promise: Promise<T>,
timeout: number
): Promise<T> {
return Promise.race([
promise,
new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error(`Timeout after ${timeout}ms`));
}, timeout);
}),
]);
}

// 使用
try {
const data = await withTimeout(fetchData(), 5000);
} catch (error) {
console.error('请求超时或失败');
}

常见面试问题

Q1: Promise 的三种状态是什么?

答案

状态说明转换
pending等待中初始状态
fulfilled已完成resolve() 触发
rejected已拒绝reject() 触发

注意:状态一旦改变就不可逆

Q2: Promise.all 和 Promise.allSettled 的区别?

答案

方法成功条件失败条件返回值
all全部成功任一失败结果数组
allSettled状态对象数组
// all - 任一失败立即失败
Promise.all([p1, p2, p3])
.then(([r1, r2, r3]) => { })
.catch((error) => { });

// allSettled - 等待全部完成
Promise.allSettled([p1, p2, p3])
.then((results) => {
results.forEach((result) => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});

Q3: async/await 的原理是什么?

答案

async/await 是 Generator + Promise 的语法糖:

// async/await
async function foo() {
const a = await Promise.resolve(1);
const b = await Promise.resolve(2);
return a + b;
}

// 等价于 Generator
function* fooGenerator() {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(2);
return a + b;
}
// + 自动执行器

Q4: 如何实现异步任务的取消?

答案

// 使用 AbortController
const controller = new AbortController();

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

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

Q5: 以下代码输出什么?

async function async1(): Promise<void> {
console.log('async1 start');
await async2();
console.log('async1 end');
}

async function async2(): Promise<void> {
console.log('async2');
}

console.log('script start');
async1();
console.log('script end');

答案

script start
async1 start
async2
script end
async1 end

分析

  1. 同步代码:script start
  2. 调用 async1:async1 start
  3. 调用 async2:async2
  4. await 后的代码放入微任务队列
  5. 同步代码:script end
  6. 执行微任务:async1 end

Q6: Promise.all、Promise.allSettled、Promise.race、Promise.any 的区别和使用场景?

答案

四个静态方法都接收一个 Promise 可迭代对象,但行为和返回值各不相同:

方法成功条件失败条件短路行为返回值
Promise.all全部 fulfilled任一 rejected遇到 reject 立即短路T[] 结果数组
Promise.allSettled无(始终 fulfilled)不短路,等所有完成PromiseSettledResult<T>[]
Promise.race第一个 settled第一个 settled第一个完成就短路第一个结果(成功或失败)
Promise.any任一 fulfilled全部 rejected遇到第一个成功就短路第一个成功的值
四种方法对比示例
// 模拟请求
const fast = (): Promise<string> =>
new Promise((resolve) => setTimeout(() => resolve('fast'), 100));
const slow = (): Promise<string> =>
new Promise((resolve) => setTimeout(() => resolve('slow'), 300));
const fail = (): Promise<string> =>
new Promise((_, reject) => setTimeout(() => reject(new Error('fail')), 200));

// ✅ Promise.all - 全部成功才返回,适合「必须全部成功」的场景
async function demoAll(): Promise<void> {
try {
const results = await Promise.all([fast(), slow()]);
console.log(results); // ['fast', 'slow']
} catch (error) {
// 任一失败就进入 catch
}
}

// ✅ Promise.allSettled - 无论成功失败都返回,适合「批量操作容忍部分失败」
async function demoAllSettled(): Promise<void> {
const results = await Promise.allSettled([fast(), fail(), slow()]);
// results 格式:
// [
// { status: 'fulfilled', value: 'fast' },
// { status: 'rejected', reason: Error('fail') },
// { status: 'fulfilled', value: 'slow' }
// ]
const succeeded = results.filter(
(r): r is PromiseFulfilledResult<string> => r.status === 'fulfilled'
);
const failed = results.filter(
(r): r is PromiseRejectedResult => r.status === 'rejected'
);
console.log(`成功 ${succeeded.length} 个,失败 ${failed.length}`);
}

// ✅ Promise.race - 最快的决定结果,适合「超时控制」
async function demoRace(): Promise<void> {
const timeout = (): Promise<never> =>
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 150)
);

try {
const result = await Promise.race([slow(), timeout()]);
console.log(result);
} catch (error) {
console.error('请求超时'); // slow 需要 300ms,超时 150ms,因此超时
}
}

// ✅ Promise.any - 第一个成功的,适合「多源竞速取最快可用」
async function demoAny(): Promise<void> {
try {
const result = await Promise.any([fail(), fast(), slow()]);
console.log(result); // 'fast'(忽略 fail,取第一个成功的)
} catch (error) {
// 全部失败才进入 catch,error 是 AggregateError
if (error instanceof AggregateError) {
console.error('全部失败:', error.errors);
}
}
}
使用场景总结
  • Promise.all:并行请求多个接口,全部成功后渲染页面(如同时获取用户信息 + 订单列表 + 配置数据)
  • Promise.allSettled:批量操作允许部分失败(如批量删除、批量上传,需要知道每个的成功/失败状态)
  • Promise.race:超时控制、取最快的 CDN 节点
  • Promise.any:多个镜像源选最快的、多个 API 备用地址容灾

Q7: async/await 的错误处理最佳实践?

答案

async/await 的错误处理有多种方式,每种适合不同场景:

方式一:try/catch(最常用)

基本 try/catch
async function fetchUserData(userId: string): Promise<User> {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
// 区分错误类型
if (error instanceof TypeError) {
console.error('网络错误:', error.message);
} else if (error instanceof SyntaxError) {
console.error('JSON 解析失败:', error.message);
} else {
console.error('请求失败:', error);
}
throw error; // 重新抛出,让调用方决定如何处理
}
}

方式二:to 函数包装(Go 风格,消除 try/catch 嵌套)

to 函数 - 推荐
type Result<T> = [null, T] | [Error, null];

async function to<T>(promise: Promise<T>): Promise<Result<T>> {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error instanceof Error ? error : new Error(String(error)), null];
}
}

// 使用:告别嵌套的 try/catch
async function handleUserAction(userId: string): Promise<void> {
const [err1, user] = await to(fetchUser(userId));
if (err1) {
showToast('获取用户失败');
return;
}

const [err2, orders] = await to(fetchOrders(user!.id));
if (err2) {
showToast('获取订单失败');
return;
}

console.log('用户和订单:', user, orders);
}

方式三:.catch() 链式处理(适合单个 await 的错误处理)

.catch() 提供默认值
async function loadConfig(): Promise<Config> {
const config = await fetchConfig().catch(() => DEFAULT_CONFIG);
// config 一定有值,不需要 try/catch
return config;
}

// 适合需要给默认值的场景
async function loadPage(): Promise<void> {
const [user, settings] = await Promise.all([
fetchUser('1').catch(() => null), // 失败则返回 null
fetchSettings().catch(() => defaults), // 失败则用默认值
]);

renderPage(user, settings);
}

方式四:全局错误处理

全局兜底
// 浏览器
window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
console.error('未处理的 Promise 拒绝:', event.reason);
event.preventDefault(); // 阻止控制台报错
// 上报错误监控
reportError(event.reason);
});

// Node.js
process.on('unhandledRejection', (reason: unknown, promise: Promise<unknown>) => {
console.error('未处理的 Promise 拒绝:', reason);
reportError(reason);
});
常见错误处理误区
  1. 不要吞掉错误:catch 中至少要记录日志或上报,不能写空的 catch 块
  2. 不要在 forEach 中使用 async/await:forEach 不会等待异步完成,用 for...ofPromise.all + map 代替
  3. 注意 await 位置return await promisereturn promise 在 try/catch 中行为不同 -- 前者能被 catch 捕获,后者不能
return vs return await 的区别
// ❌ 错误不会被 catch 捕获
async function bad(): Promise<string> {
try {
return fetchData(); // 直接返回 Promise,catch 捕获不到 fetchData 的错误
} catch (error) {
console.error(error); // 永远不会执行
return 'fallback';
}
}

// ✅ 错误能被 catch 捕获
async function good(): Promise<string> {
try {
return await fetchData(); // await 让错误能被 catch 捕获
} catch (error) {
console.error(error);
return 'fallback';
}
}

Q8: 如何实现一个可取消的 Promise(AbortController)?

答案

JavaScript 原生 Promise 不支持取消,但可以通过 AbortController 实现取消功能。AbortController 是 Web 标准 API,Node.js 15+ 也原生支持。

基础用法:取消 fetch 请求

AbortController 基础
const controller = new AbortController();
const { signal } = controller;

fetch('/api/data', { signal })
.then((res) => res.json())
.then((data) => console.log(data))
.catch((error) => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('请求失败:', error);
}
});

// 5 秒后取消
setTimeout(() => controller.abort(), 5000);

封装通用的可取消 Promise

makeCancelable 封装
interface CancelablePromise<T> {
promise: Promise<T>;
cancel: (reason?: string) => void;
}

function makeCancelable<T>(
executor: (signal: AbortSignal) => Promise<T>
): CancelablePromise<T> {
const controller = new AbortController();

const promise = new Promise<T>((resolve, reject) => {
// 如果已经取消,直接拒绝
if (controller.signal.aborted) {
reject(new DOMException('Canceled', 'AbortError'));
return;
}

// 监听取消事件
controller.signal.addEventListener('abort', () => {
reject(new DOMException(
controller.signal.reason || 'Canceled',
'AbortError'
));
});

// 执行实际逻辑
executor(controller.signal).then(resolve, reject);
});

return {
promise,
cancel: (reason?: string) => controller.abort(reason),
};
}

// 使用示例
const { promise, cancel } = makeCancelable(async (signal) => {
const res = await fetch('/api/slow-data', { signal });
return res.json();
});

promise
.then((data) => console.log('数据:', data))
.catch((err) => {
if (err.name === 'AbortError') console.log('已取消');
});

// 需要取消时
cancel('用户离开页面');

实战:React 中用 AbortController 防止内存泄漏

React Hook 中的取消
import { useState, useEffect } from 'react';

function useFetch<T>(url: string): { data: T | null; loading: boolean; error: Error | null } {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);

useEffect(() => {
const controller = new AbortController();

setLoading(true);
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((json: T) => {
setData(json);
setError(null);
})
.catch((err) => {
if (err.name !== 'AbortError') {
setError(err);
}
})
.finally(() => setLoading(false));

return () => controller.abort(); // 组件卸载时取消请求
}, [url]);

return { data, loading, error };
}

AbortController 配合超时

超时自动取消
async function fetchWithTimeout<T>(
url: string,
options: RequestInit = {},
timeout: number = 5000
): Promise<T> {
const controller = new AbortController();

// AbortSignal.timeout() 是更简洁的写法(较新 API)
// 兼容写法:手动设置定时器
const timeoutId = setTimeout(() => controller.abort('Timeout'), timeout);

try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}

// 使用
try {
const data = await fetchWithTimeout<User>('/api/user', {}, 3000);
console.log(data);
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
console.error('请求超时');
}
}
AbortSignal.any() 和 AbortSignal.timeout()

ES2024+ 新增了更便捷的 API:

  • AbortSignal.timeout(ms):创建一个超时后自动取消的 signal
  • AbortSignal.any([signal1, signal2]):任一 signal 取消时触发,类似 Promise.race
// 同时支持手动取消和超时取消
const controller = new AbortController();
const signal = AbortSignal.any([
controller.signal, // 手动取消
AbortSignal.timeout(5000), // 5 秒超时
]);

fetch('/api/data', { signal });

Q9: Promise 并发控制(限制同时执行的 Promise 数量)?

答案

在批量请求场景下(如批量上传文件、爬虫),如果不做并发限制,可能导致服务端压力过大或浏览器连接数耗尽。核心思路是维护一个执行池,池满时等待某个完成后再放入新任务。

方式一:基于 Promise.race 的 asyncPool

asyncPool 实现
async function asyncPool<T, R>(
limit: number,
items: T[],
iteratorFn: (item: T) => Promise<R>
): Promise<R[]> {
const results: R[] = [];
const executing: Set<Promise<void>> = new Set();

for (const [index, item] of items.entries()) {
// 创建任务 Promise
const p = iteratorFn(item).then((result) => {
results[index] = result; // 保持顺序
});

// 任务完成后从执行池移除
const clean = p.then(() => executing.delete(clean));
executing.add(clean);

// 达到并发上限,等待最快完成的那个
if (executing.size >= limit) {
await Promise.race(executing);
}
}

// 等待剩余任务完成
await Promise.all(executing);
return results;
}

// 使用示例
const urls = Array.from({ length: 20 }, (_, i) => `/api/item/${i}`);

const results = await asyncPool(3, urls, async (url) => {
const res = await fetch(url);
return res.json();
});
console.log(results); // 按原顺序返回 20 个结果

方式二:基于类的调度器(Scheduler)

Scheduler 调度器
class Scheduler {
private maxConcurrent: number;
private running: number = 0;
private queue: Array<() => void> = [];

constructor(maxConcurrent: number) {
this.maxConcurrent = maxConcurrent;
}

add<T>(task: () => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const run = async (): Promise<void> => {
this.running++;
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.next();
}
};

if (this.running < this.maxConcurrent) {
run();
} else {
this.queue.push(run);
}
});
}

private next(): void {
if (this.queue.length > 0 && this.running < this.maxConcurrent) {
const nextTask = this.queue.shift()!;
nextTask();
}
}
}

// 使用示例
const scheduler = new Scheduler(2); // 最多同时 2 个

const delay = (ms: number, label: string): Promise<string> =>
new Promise((resolve) => {
console.log(`${label} 开始`);
setTimeout(() => {
console.log(`${label} 完成`);
resolve(label);
}, ms);
});

scheduler.add(() => delay(1000, 'A')); // 立即执行
scheduler.add(() => delay(500, 'B')); // 立即执行
scheduler.add(() => delay(300, 'C')); // 等待,B 完成后执行
scheduler.add(() => delay(400, 'D')); // 等待,C 完成后执行

// 输出顺序: A开始 → B开始 → B完成 → C开始 → C完成 → D开始 → A完成 → D完成

方式三:用 for await + 信号量控制

信号量 Semaphore
class Semaphore {
private permits: number;
private waitQueue: Array<() => void> = [];

constructor(permits: number) {
this.permits = permits;
}

async acquire(): Promise<void> {
if (this.permits > 0) {
this.permits--;
return;
}
return new Promise<void>((resolve) => {
this.waitQueue.push(resolve);
});
}

release(): void {
this.permits++;
if (this.waitQueue.length > 0) {
this.permits--;
const next = this.waitQueue.shift()!;
next();
}
}
}

// 使用示例
async function batchFetch(urls: string[], limit: number): Promise<Response[]> {
const semaphore = new Semaphore(limit);
const tasks = urls.map(async (url) => {
await semaphore.acquire();
try {
return await fetch(url);
} finally {
semaphore.release();
}
});
return Promise.all(tasks);
}
三种方式对比
方式优点适合场景
asyncPool简洁,结果有序批量处理固定数组
Scheduler动态添加任务,可扩展运行时不断有新任务加入
Semaphore通用,可组合需要在任意位置控制并发

Q10: Generator 函数的原理,以及它和 async/await 的关系?

答案

Generator 函数是 ES6 引入的特殊函数,通过 function* 声明,内部通过 yield 关键字暂停和恢复执行。它是理解 async/await 底层原理的关键 -- async/await 本质上就是 Generator + 自动执行器的语法糖

Generator 基本原理

Generator 基础
function* counter(): Generator<number, string, boolean> {
console.log('开始');
const reset = yield 1; // 第一次暂停,返回 1
console.log('reset =', reset);
yield 2; // 第二次暂停,返回 2
return '结束'; // 完成
}

const gen = counter();

// 调用 next() 驱动执行
console.log(gen.next()); // 开始 → { value: 1, done: false }
console.log(gen.next(false)); // reset = false → { value: 2, done: false }
console.log(gen.next()); // { value: '结束', done: true }

Generator 实现异步控制

Generator 的 yield 可以暂停函数,如果 yield 的是 Promise,外部等 Promise resolve 后再调用 next(result) 把结果传回去,就实现了"用同步写法写异步":

Generator + Promise 异步
function* fetchFlow(): Generator<Promise<any>, void, any> {
const user = yield fetch('/api/user').then((r) => r.json());
console.log('用户:', user);
const orders = yield fetch(`/api/orders/${user.id}`).then((r) => r.json());
console.log('订单:', orders);
}

但 Generator 不会自动执行,需要一个执行器来驱动:

自动执行器 - co 函数
function co<T>(generatorFn: () => Generator<Promise<any>, T, any>): Promise<T> {
return new Promise((resolve, reject) => {
const gen = generatorFn();

function step(nextFn: () => IteratorResult<Promise<any>, T>): void {
let result: IteratorResult<Promise<any>, T>;
try {
result = nextFn();
} catch (error) {
reject(error);
return;
}

if (result.done) {
resolve(result.value);
return;
}

// yield 的值是 Promise,等它 resolve 后继续
Promise.resolve(result.value).then(
(value) => step(() => gen.next(value)), // 成功:传值回去
(error) => step(() => gen.throw(error)) // 失败:抛异常
);
}

step(() => gen.next());
});
}

// 使用
co(fetchFlow).then(() => console.log('全部完成'));

Generator 与 async/await 的对应关系

对比:完全等价的两种写法
// ✅ async/await 写法
async function fetchData(): Promise<void> {
try {
const user = await fetch('/api/user').then((r) => r.json());
const orders = await fetch(`/api/orders/${user.id}`).then((r) => r.json());
console.log(user, orders);
} catch (error) {
console.error(error);
}
}

// ✅ 等价的 Generator + co 写法
co(function* (): Generator<Promise<any>, void, any> {
try {
const user = yield fetch('/api/user').then((r) => r.json());
const orders = yield fetch(`/api/orders/${user.id}`).then((r) => r.json());
console.log(user, orders);
} catch (error) {
console.error(error);
}
});
对比项Generatorasync/await
声明function*async function
暂停关键字yieldawait
执行方式需要手动调用 next() 或执行器(co)引擎自动执行
返回值Generator 迭代器对象Promise
错误处理gen.throw(error)try/catch
语义通用的暂停/恢复(迭代器协议)专门用于异步
双向通信yield 可传值出去,next() 可传值进来await 只获取结果
面试要点
  1. async/await 是 Generator 的语法糖async 对应 function*await 对应 yield,引擎内置了自动执行器(类似 co 函数)
  2. Generator 比 async/await 更通用:Generator 实现了迭代器协议,可用于惰性序列、状态机等非异步场景;async/await 只用于异步
  3. Babel 转译 async/await 时,早期版本就是将其转为 Generator + 执行器的形式(可参考 regenerator-runtime
补充:Generator 的其他应用场景

Generator 不仅用于异步,还有以下经典用途:

惰性序列(无限序列)
function* fibonacci(): Generator<number, never, unknown> {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

// 按需取值,不会一次性计算所有值
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
状态机
function* trafficLight(): Generator<string, never, unknown> {
while (true) {
yield 'green';
yield 'yellow';
yield 'red';
}
}

const light = trafficLight();
console.log(light.next().value); // 'green'
console.log(light.next().value); // 'yellow'
console.log(light.next().value); // 'red'
console.log(light.next().value); // 'green'(循环)

相关链接