手动实现 Promise.all 与并发控制的异步任务调度
引言
在 JavaScript 中,Promise.all
是一个非常强大的工具,可以并发执行多个异步任务并返回结果。然而,在某些场景下,我们可能需要对并发任务的数量进行限制,以避免资源过载。本文将带你手动实现 Promise.all
的功能,并进一步扩展实现一个带有并发控制的 promiseAllWithConcurrencyLimit
函数。
1. 手动实现 Promise.all
需求描述
- 并发执行所有 Promise。
- 按照任务顺序返回结果数组。
- 如果有任务失败,立即返回第一个失败的结果。
代码实现
javascript
var promiseAll = function(functions) {
const res = new Promise((resolve, reject) => {
const res = [];
let num = 0; // res.length 并不等价于已经完成的任务数量,因为如果直接res[2] = 2,那么前面的元素会被补充null,且length等于3
for (let i = 0; i < functions.length; i++) {
const func = functions[i];
func()
.then((resolvedValue) => {
res[i] = resolvedValue;
num++;
})
.catch((rejectedValue) => {
reject(rejectedValue)
})
.finally(() => {
if (num === functions.length) {
resolve(res)
}
})
}
})
return res;
};
示例
javascript
const promise = promiseAll([() => new Promise(res => res(42))])
promise.then(console.log); // [42]
2. 带有并发控制的 promiseAllWithConcurrencyLimit
需求描述
- 接受一个数组,数组内元素均为异步任务,返回 promises。
- 对任务批量执行,每次有一个 limit 限制最大并发数量。
- 执行完成后按照一开始的任务顺序返回结果数组。
- 如果有失败返回第一个失败的结果。
模拟异步任务
javascript
function fakeFetchFn(delay, res) {
console.log(`${new Date().toLocaleTimeString()} fetching ${res}...`)
return new Promise(resolve => {
setTimeout(() => {
resolve(res);
}, delay)
})
}
const tasks = [
() => fakeFetchFn(1000, 1),
() => fakeFetchFn(1000, 2),
() => fakeFetchFn(1000, 3),
() => fakeFetchFn(1000, 4),
() => fakeFetchFn(1000, 5),
() => fakeFetchFn(1000, 6),
() => fakeFetchFn(1000, 7),
() => fakeFetchFn(1000, 8),
() => fakeFetchFn(1000, 9),
() => fakeFetchFn(1000, 10),
]
代码实现
javascript
function promiseAllWithConcurrencyLimit(tasks, limit) {
const res = new Promise((resolve, reject) => {
let numOfActive = 0;
const res = [];
let numOfCompletedTask = 0;
let index = 0;
// 递归调用
function runTask() {
while (index < tasks.length && numOfActive < limit) {
const currentIndex = index; // 保存当前索引!!!
index++; // 更新全局任务索引
const task = tasks[currentIndex];
numOfActive++;
task()
.then((resolvedValue) => {
res[currentIndex] = resolvedValue;
numOfCompletedTask++;
numOfActive--;
})
.catch((rejectedValue) => {
reject(rejectedValue)
})
.finally(() => {
if (numOfCompletedTask === tasks.length) {
resolve(res)
} else {
runTask();
}
})
}
}
runTask();
})
return res;
}
3. 示例与测试
无并发限制的测试
javascript
// without limitation, 1-10 are printed at the same time
Promise.all(tasks.map(task => task())).then(console.log);
执行结果
有并发限制的测试
javascript
// with limitation
promiseAllWithConcurrencyLimit(tasks, 2).then(console.log)
执行结果
4. 总结
通过本文,我们实现了以下功能:
- 手动实现
Promise.all
:能够并发执行多个异步任务,并按照顺序返回结果。 - 并发控制的异步任务调度 :通过
promiseAllWithConcurrencyLimit
,可以限制并发任务数量,优化资源使用。
这些技巧在实际开发中非常有用,尤其是在需要优化资源使用的场景中。希望本文能帮助你更好地理解 Promise 和并发控制的实现原理!
关键词
Promise.all
、并发控制、异步任务、JavaScript、Promise、任务调度