资讯专栏INFORMATION COLUMN

实现一个并发数可变的 Promise.all 静态方法

codeGoogle / 1909人阅读

摘要:在这种情况下,马上可以想到的一个方法是我们可以使用队列的数据结构来实现调度粒度为的方法。

实现一个并发数可变的 Promise.all 静态方法
Promise.all (iterable): The all function returns a new promise which is fulfilled with an array of fulfillment values for the passed promises, or rejects with the reason of the first passed promise that rejects. It resolves all elements of the passed iterable to promises as it runs this algorithm.

Promise.all 静态方法具有如下特性:

接收一个 Promise 实例的数组或具有 Iterator 接口的对象

如果元素不是 Promise 对象则使用 Promise.resolve 转成 Promise 对象

如果全部成功状态变为 resolved,返回值将有序组成一个数组传给回调

只要有一个失败状态就变为 rejected,返回值将直接传递给回调

该方法的返回值是一个新的 Promise 对象

下面是 Promise.all 的简单用法:

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
    .then((results) => {
        console.log(results); // [1, 2, 3]
    });
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
    .then((results) => {
        console.log(results);
    }).catch((e) => {
        console.log(e); // 2
    });
如何实现 Promise.all 静态方法
Promise.all = Promise.all || function(promises) {
    // 如果实参不是数组则报错返回
    if (!isArray(promises)) {
        throw new TypeError("You must pass an array to all.");
    }

    // 结果返回一个新的 Promise 实例
    return new Promise(function(resolve, reject) {
        var i = 0,
            result = [],
            len = promises.length,
            count = len

        // 使用闭包记录数组执行顺序
        function resolver(index) {
            return function(value) {
                resolveAll(index, value);
            };
        }

        // 只要有一个失败状态就变为 rejected
        function rejecter(reason) {
            reject(reason);
        }

        // 如果全部成功状态变为 resolved
        function resolveAll(index, value) {
            result[index] = value;
            if (--count == 0) {
                resolve(result)
            }
        }

        // 遍历数组并发执行异步代码
        for (; i < len; i++) {
            promises[i].then(resolver(i), rejecter);
        }
    });
}
实现一个调度粒度可变的 Promise.all 静态方法
那么回到题目的问题:如何实现一个调度粒度可变的 Promise.all 静态方法呢?这里首先可能会产生一个疑问就是什么叫调度粒度可变,实际上很简单:就是给 all 方法增加一个正整数类型的参数,用来标识传入的 Promise 实例数组中可以并发执行的最大个数。

举例如下,声明三个不同异步时间的 Promise 实例并调用 all 方法,正常情况下(我们粗暴的认为)其执行时间应该为200ms(当然这是一个错误的答案,在这个例子当中我们仅考虑并发逻辑),而在调度粒度为1的 all 方法中其执行时间应该为450ms,在调度粒度为2的 all 方法中其执行时间应该为250ms,以此类推。

const p1 = new Promise((resolve) => setTimeout(() => resolve(1), 150));
const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 200));
const p3 = new Promise((resolve) => setTimeout(() => resolve(3), 100));
Promise.all([p1, p2, p3]).then((r) => console.log(r));

在这种情况下,马上可以想到的一个方法是:我们可以使用队列的数据结构来实现调度粒度为1的 all 方法

Promise.all1 = (promises) => {
    if (!isArray(promises)) {
        throw new TypeError("You must pass an array to all.");
    }

    return new Promise((resolve, reject) => {
        const _q = [...promises];
        const result = [];

        function resolver(value) {
            result.push(value);
            next();
        }

        function rejecter(reason) {
            reject(reason);
        }

        function next() {
            if (_q.length) {
                _q.shift().then(resolver, rejecter);
            } else {
                resolve(result);
            }
        }

        next();
    });
}

写到这儿不难发现,不同调度粒度实际上是对队列每次推出的 Promise 实例数量最大值的约束,以及对返回结果的顺序索引作出缓存,那么把代码进行简单的修改即可实现预期的功能。

Promise.all2 = (promises, concurrent = promises.length) => {
    if (!Array.isArray(promises)) {
        throw new TypeError("You must pass an array to all.");
    }

    if (concurrent < 1) {
        return Promise.reject();
    }

    return new Promise((resolve, reject) => {
        const queue = [...promises];
        const result = [];
        let total = promises.length;
        let count = concurrent;
        let index = 0;

        function resolver(index) {
            return function(value) {
                resolveAll(index, value);
            };
        }

        function resolveAll(index, value) {
            result[index] = value;
            count++;
            total--;
            next();
            
            if (!total) {
                resolve(result);
            }
        }

        function rejecter(reason) {
            reject(reason);
        }

        function next() {
            while (queue.length && count > 0) {
                count--;
                (queue.shift())().then(resolver(index++), rejecter);
            }
        }

        next();
    });
};
当然这里有一个比较吊诡的地方!如果我们按照上面的代码进行 Promise 实例的声明,那么在执行到 all 方法之前它们就已经在并发执行,也就不会有“不同调度粒度”之说,所以为了实现我们预期的功能需要在 all 方法内部把这些 Promise 实例初始化出来。
function promiseFactory(fn) {
    return function() {
        return new Promise(fn);
    };
}

const p1 = promiseFactory((resolve) => setTimeout(() => resolve(1), 1500));
const p2 = promiseFactory((resolve) => setTimeout(() => resolve(2), 2000));
const p3 = promiseFactory((resolve) => setTimeout(() => resolve(3), 1000));
console.time("hello world!");

Promise.all2([p1, p2, p3], 1).then((r) => {
    console.log(r)
    console.timeEnd("hello world!"); // 4500+ms打印结果
});

Promise.all2([p1, p2, p3], 2).then((r) => {
    console.log(r)
    console.timeEnd("hello world!"); // 2500+ms打印结果
});

Promise.all2([p1, p2, p3]).then((r) => {
    console.log(r)
    console.timeEnd("hello world!"); // 2000+ms打印结果
});

为了能看出明显的区别我们把定时器的时间延长到秒,执行代码后完美验证。

至此结束。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94333.html

相关文章

  • Promise.all并发限制

    摘要:并发限制指的是,每个时刻并发执行的数量是固定的,最终的执行结果还是保持与原来的一致。换句话说,就是把生成数组的控制权,交给并发控制逻辑。 背景 通常,我们在需要保证代码在多个异步处理之后执行,会用到: Promise.all(promises: []).then(fun: function); Promise.all可以保证,promises数组中所有promise对象都达到resol...

    sutaking 评论0 收藏0
  • 多个请求并发执行怎么写?

    摘要:最近在写一个程序,功能是下载页面上的资源,首先拿到页面资源链接列表,如要求是资源并行下载,所有资源下载结束后通知,收集错误的下载链接。如果把上面模拟请求的注释去掉,还会发现是在结束后就执行了,而后面的请求还未结束。 最近在写一个Node.js程序,功能是下载页面上的资源,首先拿到页面资源链接列表,如: [ https://xxx.com/img/logo.jpg, https:...

    gself 评论0 收藏0
  • JavaScript 异步

    摘要:第二种则一定会执行所有的异步函数,即便你需要使用的是这些高阶函数。并发实现的异步数组然后修改,使用即可上面的其他内容终结整个链式操作并返回结果这里使用是为了兼容的调用方式调用方式不变。 JavaScript 异步数组 吾辈的博客原文: https://blog.rxliuli.com/p/5e... 场景 吾辈是一只在飞向太阳的萤火虫 JavaScript 中的数组是一个相当泛用性的...

    moven_j 评论0 收藏0
  • 实现 JavaScript 异步方法 Promise.all

    摘要:本次的任务假如。。。。。引擎发生了重大故障,方法变成了,为了拯救世界,需要开发一个模块来解决此问题。实现首先要知道是什么是对异步编程的一种抽象。数组中任何一个为的话,则整个调用会立即终止,并返回一个的新的对象。 本次的任务 假如。。。。。 JavaScript v8 引擎发生了重大故障,Promise.all 方法变成了 undefined ,为了拯救 JavaScript 世界,需要...

    mushang 评论0 收藏0
  • 第10章:并发和分布式编程 10.1并发性和线程安全性

    摘要:并发模块本身有两种不同的类型进程和线程,两个基本的执行单元。调用以启动新线程。在大多数系统中,时间片发生不可预知的和非确定性的,这意味着线程可能随时暂停或恢复。 大纲 什么是并发编程?进程,线程和时间片交织和竞争条件线程安全 策略1:监禁 策略2:不可变性 策略3:使用线程安全数据类型 策略4:锁定和同步 如何做安全论证总结 什么是并发编程? 并发并发性:多个计算同时发生。 在现代...

    instein 评论0 收藏0

发表评论

0条评论

codeGoogle

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<