• JS异步任务的并行、串行,以及二者结合


    让多个异步任务按照我们的想法执行,是开发中常见的需求。今天我们就来捋一下,如何让多个异步任务并行,串行,以及并行串行相结合。

     

    一、并行

    并行是使用最多的方式,多个相互间没有依赖关系的异步任务,并行执行能够提高效率。

    我们最经常用的,是Promise.all() 。

    复制代码
    function f1() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('1结束');
                resolve();
            }, 1000)
        });
    }
    function f2() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('2结束');
                resolve();
            }, 900)
        });
    }
    function f3() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('3结束');
                resolve();
            }, 800)
        });
    }
    
    let arr = [f1, f2, f3];
    Promise.all(arr.map(i => i()));
    // 3结束
    // 2结束
    // 1结束
    复制代码

    以下几种数组遍历方式,同样可以实现并行。

    复制代码
    // forEach遍历
    arr.forEach(item => {
        item();
    });
    // for循环
    for (let i = 0; i < arr.length; i++) {
        arr[i]();
    }
    // for...of遍历
    for (let item of arr) {
        item();
    }
    // 注意,以下两种写法同样是并行的 arr.forEach(async item => await item()); async function f() { arr.forEach(async item => await item()) } f();
    复制代码

    相比之下,Promise.all()可以确保任务都执行成功,然后再执行后续操作,这是各种遍历无法做到的。

    另外,还有一种方式也能实现并行:Promise.allSettled()。

    Promise.allSettled(arr.map(i => i()));

    这种方式很特别,它无法得到每个Promise对象的返回值,却可以精确得知每个任务的成功还是失败。如果你有这样的需求场景,用Promise.allSettled()就很合适。

     

    二、串行

    我在工作中遇到过一个场景,一个有1000+元素的数组,每个成员都是调用第三方接口的Promise对象。我像往常一样得意的使用Promise.all(),等着1000多个任务瞬间完成。然而,结果却让我大跌眼镜,这1000多个任务,只有一部分成功了,大部分都报错了。不管我执行几次,结果都是这个样。一筹莫展之后,我才从第三方那儿得知,他们的接口是有调用限制的,一个接口同一时间只能并行300个。

    有没有办法能让它们一个接一个的执行呢?也就是串行。

    nodejs koa框架的next()语法给了我启发,它就是让中间件一个接一个的执行。于是我想出了递归的方式:

    复制代码
    async function serial(arr) {
        let item = arr.shift();
    
        await item();
        if (arr.length > 0) {
            await serial(arr);
        }
    }
    serial(arr);
    // 1结束
    // 2结束
    // 3结束
    复制代码

    其实,想让异步任务串行,不用这么麻烦。以下遍历的方式,同样可以实现串行。

    复制代码
    // 使用for...of
    async function f() {
        for (let item of arr) {
            await item();
        }
    }
    f();
    
    // 使用for循环
    async function f() {
        for (let i = 0; i < arr.length; i++) {
            await arr[i]();
        }
    }
    f();
    复制代码

    发现了没?为什么同样是for循环,同样是for...of,前面的写法是并行,后面就成了串行呢?

    工作中,我们一定做过这样的尝试,想通过遍历,来让多个异步任务串行。但往往不得其法,怎么折腾它们都还是同时执行。

    后一种写法,你可以理解为:await执行完成后,才会进入下一次循环。 其实,遍历,就相当于把每一个元素,在代码中从上到下写下来。当它们处于async函数中,并在每个元素前面加await,它们自然就能顺序执行。否则,我们都知道,简单的顺序写下来的异步任务,它们还是同时执行的。

    好了,现在程序不报错了。但是,1000多个任务依次执行完成,足足花了十多分钟,太慢了!有没有办法,又快又不触发接口调用限制呢?

    有,如果可以并行200个任务,完成后再开始下一轮200个......也就是,把并行和串行相结合。

     

    三、并行串行结合

    复制代码
    async function bingChuan(arr, num) {
        let items = arr.splice(0, num);
        
        await Promise.all(items.map(i => i()));
        if (arr.length > 0) {
            await bingChuan(arr, num);
        }
    }
    bingChuan(arr, 2);
    // 2结束
    // 1结束
    // 3结束
    复制代码

    好了,现在可以同时享有并行和串行的好处了!

     

    本人水平非常有限,写作主要是为了把自己学过的东西捋清楚。如有错误,还请指正,感激不尽。

  • 相关阅读:
    Zebec Protocol 成非洲利比亚展会合作伙伴,并将向第三世界国家布局
    测试工程师需要具备哪些“技能”?
    本宁顿学院青年作家奖报名备赛
    Java从入门到架构师__常用工具&常用API&本地缓存&RestTemplate
    Android 四大组件 -- Activity
    GPT-4 Turbo 发布 | 大模型训练的新时代:超算互联网的调度与调优
    EDAS 流量入口网关最佳实践
    京东API接口(商品详情页采集+关键词搜索商品列表):开启电商业务的新篇章
    electron应用打包成功纪念一下
    笛卡尔树【模板】
  • 原文地址:https://www.cnblogs.com/luzeyu/p/17790599.html