异步编程手写题
掌握 Promise 相关方法和异步并发控制
1. Promise.all / race / allSettled
难度: ⭐⭐⭐ | 梯队: 第一梯队 ✅ | 标签: 异步, Promise
题目描述
实现 Promise.all、Promise.race、Promise.allSettled 三个静态方法。
- Promise.all: 所有 Promise 都成功才 resolve,任一失败立即 reject
- Promise.race: 任意一个 Promise 完成(成功或失败)就返回
- Promise.allSettled: 等待所有 Promise 完成,无论成功失败
代码实现
javascript
/**
* Promise.all 实现
* 所有成功才 resolve,任一失败就 reject
* @param {Iterable} promises
* @return {Promise}
*/
Promise.myAll = function (promises) {
return new Promise((resolve, reject) => {
// 处理非数组输入(可迭代对象)
const promiseArray = Array.from(promises);
const results = new Array(promiseArray.length);
let resolvedCount = 0;
// 空数组直接返回
if (promiseArray.length === 0) {
resolve(results);
return;
}
promiseArray.forEach((promise, index) => {
// 使用 Promise.resolve 包装,处理非 Promise 值
Promise.resolve(promise)
.then((value) => {
results[index] = value; // 保持顺序
resolvedCount++;
if (resolvedCount === promiseArray.length) {
resolve(results);
}
})
.catch(reject); // 任一失败立即 reject
});
});
};
/**
* Promise.race 实现
* 任意一个完成就返回(成功或失败)
* @param {Iterable} promises
* @return {Promise}
*/
Promise.myRace = function (promises) {
return new Promise((resolve, reject) => {
const promiseArray = Array.from(promises);
// 空数组永远 pending(符合规范)
for (const promise of promiseArray) {
Promise.resolve(promise).then(resolve, reject);
}
});
};
/**
* Promise.allSettled 实现
* 等待所有 Promise 完成(无论成功失败)
* @param {Iterable} promises
* @return {Promise}
*/
Promise.myAllSettled = function (promises) {
return new Promise((resolve) => {
const promiseArray = Array.from(promises);
const results = new Array(promiseArray.length);
let settledCount = 0;
if (promiseArray.length === 0) {
resolve(results);
return;
}
promiseArray.forEach((promise, index) => {
Promise.resolve(promise)
.then(
(value) => {
results[index] = { status: 'fulfilled', value };
},
(reason) => {
results[index] = { status: 'rejected', reason };
}
)
.finally(() => {
settledCount++;
if (settledCount === promiseArray.length) {
resolve(results);
}
});
});
});
};
/**
* Promise.any 实现(补充)
* 任意一个成功就返回,全部失败才 reject
*/
Promise.myAny = function (promises) {
return new Promise((resolve, reject) => {
const promiseArray = Array.from(promises);
const errors = new Array(promiseArray.length);
let rejectedCount = 0;
if (promiseArray.length === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
promiseArray.forEach((promise, index) => {
Promise.resolve(promise).then(resolve, (reason) => {
errors[index] = reason;
rejectedCount++;
if (rejectedCount === promiseArray.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
};示例调用
javascript
// ========== Promise.all ==========
// 基础示例:全部成功
const allResult = await Promise.myAll([
Promise.resolve(1),
Promise.resolve(2),
new Promise((r) => setTimeout(() => r(3), 100)),
]);
console.log(allResult); // [1, 2, 3]
// 边界条件:空数组
console.log(await Promise.myAll([])); // []
// 边界条件:包含非 Promise 值
console.log(await Promise.myAll([1, 2, 3])); // [1, 2, 3]
// 边界条件:任一失败
try {
await Promise.myAll([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3),
]);
} catch (e) {
console.log(e); // 'error'
}
// ========== Promise.race ==========
// 基础示例
const raceResult = await Promise.myRace([
new Promise((r) => setTimeout(() => r('slow'), 200)),
new Promise((r) => setTimeout(() => r('fast'), 100)),
]);
console.log(raceResult); // 'fast'
// 边界条件:有 reject
const raceReject = await Promise.myRace([
new Promise((r) => setTimeout(() => r('slow'), 200)),
Promise.reject('error'),
]).catch((e) => e);
console.log(raceReject); // 'error'
// ========== Promise.allSettled ==========
const settledResult = await Promise.myAllSettled([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3),
]);
console.log(settledResult);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]复杂度分析
- 时间: O(n)
- 空间: O(n)
10. 请求并发控制
难度: ⭐⭐⭐ | 梯队: 第二梯队 | 标签: 异步, 队列
题目描述
批量上传图片、批量请求接口时需限制并发数,避免浏览器连接数超限或服务端压力过大。实现类似 p-limit 的并发控制函数:创建一个执行器,最多同时运行 concurrency 个异步任务,超出部分进入队列等待。
代码实现
javascript
/**
* 请求并发控制
* @param {number} concurrency - 最大并发数
* @return {Function} - 返回 (fn) => (...args) => Promise,包装后的函数
*/
function pLimit(concurrency) {
if (concurrency < 1) {
throw new Error('concurrency must be >= 1');
}
let activeCount = 0;
const queue = [];
const run = async (fn, resolve, reject, ...args) => {
activeCount++;
try {
const result = await fn(...args);
resolve(result);
} catch (error) {
reject(error);
} finally {
activeCount--;
// 队列中有等待的任务,取出执行
if (queue.length > 0) {
const next = queue.shift();
run(next.fn, next.resolve, next.reject, ...next.args);
}
}
};
const enqueue = (fn, resolve, reject, ...args) => {
queue.push({ fn, resolve, reject, args });
};
const limit = (fn) => {
return function (...args) {
return new Promise((resolve, reject) => {
if (activeCount < concurrency) {
run(fn, resolve, reject, ...args);
} else {
enqueue(fn, resolve, reject, ...args);
}
});
};
};
return limit;
}
/**
* 直接执行任务的版本
* limit((url) => fetch(url)) 返回一个函数,调用时自动限流
*/
function pLimitDirect(concurrency) {
let activeCount = 0;
const queue = [];
const run = async (task) => {
activeCount++;
try {
const result = await task();
return result;
} finally {
activeCount--;
if (queue.length > 0) {
const next = queue.shift();
run(next);
}
}
};
return (task) => {
return new Promise((resolve, reject) => {
const wrapped = () => Promise.resolve(task()).then(resolve, reject);
if (activeCount < concurrency) {
run(wrapped);
} else {
queue.push(wrapped);
}
});
};
}示例调用
javascript
// 模拟异步任务
const delay = (ms, value) =>
new Promise((r) => setTimeout(() => r(value), ms));
const limit = pLimit(2);
// 包装异步函数
const limitedFetch = limit(async (id) => {
console.log(`start ${id}`);
await delay(100, id);
console.log(`end ${id}`);
return id;
});
// 同时最多 2 个在执行
Promise.all([
limitedFetch(1),
limitedFetch(2),
limitedFetch(3),
limitedFetch(4),
]).then((results) => console.log(results)); // [1, 2, 3, 4]
// 输出顺序: start 1, start 2, end 1, start 3, end 2, start 4, end 3, end 4
// 边界条件:并发数为 1(串行)
const limit1 = pLimit(1);
const serial = limit1((x) => delay(50, x));
Promise.all([serial(1), serial(2), serial(3)]).then(console.log); // [1, 2, 3]
// 边界条件:任务失败不影响其他任务
const limit2 = pLimit(2);
const mayFail = limit2(async (x) => {
if (x === 2) throw new Error('fail');
return x;
});
Promise.allSettled([
mayFail(1),
mayFail(2),
mayFail(3),
]).then(console.log);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: Error: fail },
// { status: 'fulfilled', value: 3 }
// ]复杂度分析
- 时间: O(n),n 为任务数
- 空间: O(concurrency),队列最大长度为待执行任务数