手动实现 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、任务调度


相关推荐
拾年2752 小时前
React Hooks 进阶篇:useMemo / useCallback / useReducer / useImperativeHandle,用「算账 +
前端·javascript·react.js
Jackson__2 小时前
面试官:如何在老项目中使用新框架,并实现通信?
前端·javascript·面试
拾年2752 小时前
React Hooks 保姆级教程:把组件想象成失忆的打工人,4 个 Hook 带你原地起飞
前端·javascript·react.js
默_笙2 小时前
😝 5 亿次循环卡死页面?我用 Web Worker 把它扔进后台线程,页面丝滑如初
前端·javascript
CAD老兵2 小时前
一行代码集成 DWG/DXF 图纸查看:测量批注,数据不出站
前端·javascript·github
小林ixn2 小时前
单例模式:从弹窗管理到全局状态,一个模式搞定
前端·javascript·设计模式
名字还没想好☜3 小时前
React 用 useEffect 做轮询实战:setInterval 拿到旧 state 的闭包陷阱与正确清理
前端·javascript·react.js·react·useeffect
SendTomo4 小时前
P2P文件传输工具对比:SendTomo vs send.wang
javascript·网络协议·webrtc·html5·p2p
SendTomo6 小时前
WebRTC文件互传工具实测对比
javascript·网络·webrtc·html5·p2p