手动实现 promiseAll与带有并发控制的promiseAllWithConcurrencyLimit

手动实现 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. 总结

通过本文,我们实现了以下功能:

  1. 手动实现 Promise.all:能够并发执行多个异步任务,并按照顺序返回结果。
  2. 并发控制的异步任务调度 :通过 promiseAllWithConcurrencyLimit,可以限制并发任务数量,优化资源使用。

这些技巧在实际开发中非常有用,尤其是在需要优化资源使用的场景中。希望本文能帮助你更好地理解 Promise 和并发控制的实现原理!


关键词

Promise.all、并发控制、异步任务、JavaScript、Promise、任务调度


相关推荐
山河木马27 分钟前
矩阵专题3-怎么创建投影矩阵(uProjectionMatrix)
javascript·webgl·计算机图形学
泯泷2 小时前
第 2 篇:设计第一套字节码:Opcode、Instruction 与 Constant Pool
前端·javascript·安全
泯泷2 小时前
第 1 篇:从 1 + 2 开始:亲手写出第一台 JSVM
前端·javascript·安全
朦胧之3 小时前
页面白屏卡住排查方法
前端·javascript
犇驫聊AI4 小时前
Chrome DevTools MCP + Claude Code 自定义skills生成接口代码生成器
前端·javascript
kyriewen4 小时前
别再这样写 async/await 了:我在 Code Review 中见过最多的 8 个错误
前端·javascript·面试
用户298698530149 小时前
在 React 中使用 JavaScript 将 Excel 转换为 SVG
前端·javascript·react.js
labixiong9 小时前
手写Promise--微任务、静态方法、async/await 全搞懂(三)
前端·javascript
铁皮饭盒11 小时前
3行代码搞定页面截图,Bun.WebView真的简单
javascript
kyriewen1 天前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试