黑基网 首页 公开课 编程课程 查看内容

如何优雅地写js异步循环

2017-7-20 11:59| 投稿: lofor| 查看: 3300| 评论: 87|来自: 互联网

摘要: 这篇文章作为之前两篇文章的延续,来的稍微有点迟。如何优雅地写js异步代码如何优雅地写js异步代码(2)时隔一年,以上两篇文章内容或有过时,请读者自行斟酌。好下面正式开始本文内容。循环的方式假设我们有个数组, ...

     友情提醒:公开课需要专用客户端播放,否则只能播放10多秒!点击下载    

这篇文章作为之前两篇文章的延续,来的稍微有点迟。

时隔一年,以上两篇文章内容或有过时,请读者自行斟酌。好下面正式开始本文内容。

循环的方式

假设我们有个数组,包含 5 个数字: let times = [100, 150, 200, 250, 300] ;

还有一个异步的睡觉方法: sleep(time, cb) 。

import Promise from 'bluebird';

// 当没有 cb 时,返回一个 Promise 对象
export default function sleep(time, cb) {  
    if (cb) {
        setTimeout(cb, time);
    } else {
        return new Promise(resolve => {
            setTimeout(resolve, time);
        });
    }
};

现在要去循环睡这几个数字,问你有哪些睡法?��

为了方便交流,我就给这几个睡法起个名字:

  1. All in:你如果赶时间又不担心消耗过度,你可以一次性都睡了;
  2. One by one:你想细水长流,你可以一个一个睡;
  3. With concurrency:你害羞地低下头,说一次能不能睡两个。

作为一段有节操的代码,肯定要告诉其他人你睡完了,也就是必须有全部完成的回调,否则我们接下来的交流会毫无意义。

本文目的是和大家探讨如何写出优雅的异步循环代码,并不是去实现这些循环控制的逻辑;而保持代码优雅,个人以为最好的办法是使用较新的语言特性,其次是使用优秀的开源项目,最后才是自己撸。下面会使用 Async 、 Promise(bluebird) 和 ES7 中的 async/await 对比下实现这几种循环的区别。

All in

这种方式效率是最高的,耗时取决于循环中最慢的那个异步方法。对资源的消耗也是最大的,如果大量循环请求后端服务,很有可能造成瞬时拥堵的情况。

如果自己实现,这也是最简单的场景,加一个完成计数器,每个异步方法完就给这个完成计数器加 1,然后检查完成数是不是等于数组长度,一旦相等就表示所有的异步方法执行完毕,通知全部完成的回调。

使用 async.each:

import { each } from 'async';  
import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

console.log('sleep start');  
console.time('async all in');  
each(times, sleep, (err) => {  
    console.timeEnd('async all in');
    console.log('sleep complete');
});
// sleep start
// async all in: 304.627ms
// sleep complete

使用 Promise.all:

import Promise from 'bluebird';  
import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

console.log('sleep start');  
console.time('promise all in');  
Promise.all(times.map(time => sleep(time))).then(() => {  
    console.timeEnd('promise all in');
    console.log('sleep complete');
});
// sleep start
// promise all in: 305.509ms
// sleep complete

使用ES7 async/await:

import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

(async function() {
    console.log('sleep start');
    console.time('es7 all in');
    for await (let i of times.map(time => sleep(time))) {}
    console.timeEnd('es7 all in');
    console.log('sleep complete');
}());
// sleep start
// es7 all in: 305.986ms
// sleep complete

One by one

这种方式效率最低,有点类似于同步语言中的循环,一个接着一个执行,耗时自然也就是所有异步方法耗时的总和。对资源的消耗最小。

这个实现起来也比较简单,把数组看做一个队列,每次从队列 shift 出一个代入异步方法执行,执行完成就开始递归调用这个过程,当队列长度为空就表示所有的异步方法执行完毕,结束递归,通知全部完成的回调。

使用 async.eachSeries:

import { eachSeries } from 'async';  
import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

console.log('sleep start');  
console.time('async one by one');  
eachSeries(times, sleep, (err) => {  
    console.timeEnd('async one by one');
    console.log('sleep complete');
});
// sleep start
// async one by one: 1020.078ms
// sleep complete

使用 Promise.reduce:

import Promise from 'bluebird';  
import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

console.log('sleep start');  
console.time('promise one by one');  
Promise.reduce(times, (last, curr) => {  
    return sleep(curr);
}, 0).then(() => {
    console.timeEnd('promise one by one');
    console.log('sleep complete');
});
// sleep start
// promise one by one: 1023.014ms
// sleep complete

使用ES7 async/await:

import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

(async function() {
    console.log('sleep start');
    console.time('es7 one by one');
    for (let time of times) {
        await sleep(time);
    }
    console.timeEnd('es7 one by one');
    console.log('sleep complete');
}());
// sleep start
// es7 one by one: 1025.513ms
// sleep complete

With concurrency

这种方式稍微复杂些,但也是最灵活的方式,可以随心控制并发数。效率和耗时取决于魔法数字 concurrency ,当 concurrency 大于或等于数组长度时,它就等同于 All in 方式;当 concurrency 为 1 时,它就等同于 One by one 方式。所以耗时和对资源的消耗都会介于以上两种方式之间。

With concurrency本身在实现上也会有不同的方式,分别是预分组和任务池。

预分组

顾名思义,就是提前将数组内容按 concurrency 分好组,组内是以 All in 方式执行,组之间则是以 One by one 的方式执行。

就以上文的例子,假如 concurrency 为 2, times 预先分组成: [[100, 150], [200, 250], [300]] ,这样耗时会是 700(150 + 250 + 300)。

这个实现方式可以有效地控制并发数,优点就是简单,缺点是并不能达到效率最大化。

任务池

任务池的方式就是设置一个容量为 concurrency 的池子,比如容量为 2,初始化放入两个任务,每当有任务完成,就继续往池子添加新的任务,直到所有任务都完成。上文的例子执行过程大致如下:

  1. time = 0; pool = [100, 150] :放入 100 和 150
  2. time = 100; pool = [150, 200] : 100 结束,放入 200
  3. time = 150; pool = [200, 250] : 150 结束,放入 250
  4. time = 300; pool = [250, 300] : 200 结束,放入 300
  5. time = 400; pool = [300] : 250 结束,没有更多任务
  6. time = 600; pool = [] : 300 结束,循环完毕

得出来的耗时是 600,比预分组的方式效率更高,而且同样能有效控制并发个数。async 和 bluebird 也有相关的方法供直接使用。

使用 async.eachLimit:

import { eachLimit } from 'async';  
import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

console.log('sleep start');  
console.time('async with concurrency');  
eachLimit(times, 2, sleep, (err) => {  
    console.timeEnd('async with concurrency');
    console.log('sleep complete');
});
// sleep start
// async with concurrency: 611.498ms
// sleep complete

使用 Promise.map(bluebird 特有 api):

import Promise from 'bluebird';  
import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

console.log('sleep start');  
console.time('promise one by one');  
Promise.map(times, (time) => {  
    return sleep(time);
}, {
  concurrency: 2
}).then(() => {
  console.timeEnd('promise one by one');
  console.log('sleep complete');
});
// sleep start
// promise with concurrency: 616.601ms
// sleep complete

使用ES7 async/await:

pool 方法来自 davetemplin/async-parallel

import sleep from './sleep';

let times = [100, 150, 200, 250, 300];

(async function() {
    console.log('sleep start');
    console.time('es7 with concurrency');
    await pool(2, async () => {
        await sleep(times.shift());
        return times.length > 0;
    });
    console.timeEnd('es7 with concurrency');
    console.log('sleep complete');
}());

async function pool(size, task) {  
    var active = 0;
    var done = false;
    var errors = [];
    return new Promise((resolve, reject) => {
        next();
        function next() {
            while (active < size && !done) {
                active += 1;
                task()
                    .then(more => {
                        if (--active === 0 && (done || !more))
                            errors.length === 0 ? resolve() : reject(errors);
                        else if (more)
                            next();
                        else
                            done = true;
                    })
                    .catch(err => {
                        errors.push(err);
                        done = true;
                        if (--active === 0)
                            reject(errors);
                    });
            }
        }
    });
}
// sleep start
// es7 with concurrency: 612.197ms
// sleep complete

总结

好了,到这应该可以给这三种循环方式打下分了:

循环方式效率消耗灵活度复杂度
All in
One by one
With concurrency

乍一看 With concurrency 是完胜,其实并没有。 All in 和 One by one 虽然灵活度低,但是应用的场景还是非常广泛的。要求效率优先就使用 All in ;如果有下一次循环依赖上一次循环结果的场景,就必须使用 One by One 。

再说下上面 async、bluebird、ES7 对这三种循环方式的实现。需求一直在变,async 需要修改的代码非常少,甚至只要改下方法名就可以,方法定义简单优雅,这可能也是 async 易上手的原因;bluebird 在 Promise 标准基础上添加的方法非常实用,如:map、join...,以至于我几乎是没有使用过原生 Promise :joy:;ES7 新增的 async/await 语法特性确实减轻了编写异步代码的痛苦,同时还增强了代码的可读性。

So,你觉得哪种写法更优雅呢?


鲜花

握手

雷人

路过

鸡蛋

相关阅读

发表评论

最新评论

引用 游客 2018-2-25 14:38
Your home is valueble for me. Thanks!?
michael kors outlet http://www.michaelkors-outletfactory.us.org
引用 游客 2018-2-25 00:50
Thank you a lot for providing individuals with a very special opportunity to check tips from this web site. It is often very pleasant and also packed with a great time for me personally and my office fellow workers to search your site more than three times in 7 days to study the new secrets you have. And indeed, I'm also certainly satisfied with the sensational tips and hints you serve. Certain two facts on this page are surely the finest I have had.
yeezy boost 350 http://42.herber.pl/yeezyboo ...
引用 游客 2018-2-24 23:50
very nice publish, i actually love this website, keep on it
Adidas NMD New Geometry Black Grey http://www.adidas-nmds.us.com/adidas-nmd-new-geometry-black-grey-p-541.html
引用 游客 2018-2-24 09:04
An fascinating discussion is price comment. I feel that it's best to write more on this topic, it may not be a taboo topic however typically persons are not sufficient to speak on such topics. To the next. Cheers
cheap nhl jerseys http://www.nhljerseys.us.org
引用 游客 2018-2-23 07:20
Once I initially commented I clicked the -Notify me when new comments are added- checkbox and now each time a remark is added I get four emails with the same comment. Is there any manner you may remove me from that service? Thanks!
michael kors handbags http://www.michael-kors-handbags.com.co
引用 游客 2018-2-23 04:04
My spouse and i have been quite lucky that Chris managed to deal with his basic research out of the precious recommendations he grabbed out of the web page. It is now and again perplexing to just be handing out methods  others have been trying to sell. And we fully understand we now have you to thank for that. All the explanations you made, the straightforward blog navigation, the relationships your site aid to promote - it is everything great, and it's really assisting our son in addition to us ...
引用 游客 2018-2-22 06:02
Spot on with this write-up, I really assume this web site wants much more consideration. I抣l probably be again to learn way more, thanks for that info.
adidas neo http://www.adidasneo.us.com
引用 游客 2018-2-21 06:31
I wish to point out my gratitude for your generosity supporting persons who actually need guidance on in this idea. Your very own commitment to passing the solution all through appeared to be wonderfully effective and have without exception empowered guys like me to arrive at their dreams. Your entire helpful publication signifies a great deal to me and a whole lot more to my office colleagues. Thanks a lot; from each one of us.
yeezy boost 350 http://tiny.gl/yzyshoes
引用 游客 2018-2-21 04:29
Can I simply say what a aid to search out someone who actually knows what theyre talking about on the internet. You undoubtedly know the way to convey a difficulty to gentle and make it important. Extra people must learn this and understand this facet of the story. I cant consider youre no more fashionable because you undoubtedly have the gift.
led shoes http://www.ledshoes.us.com
引用 游客 2018-2-20 04:46
Nice post. I be taught something tougher on different blogs everyday. It would all the time be stimulating to read content material from other writers and practice a little bit one thing from their store. I抎 prefer to make use of some with the content on my blog whether you don抰 mind. Natually I抣l give you a link on your web blog. Thanks for sharing.
supreme clothing http://www.supremeclothing.us
引用 游客 2018-2-19 12:54
I am only commenting to make you understand what a terrific experience my friend's girl found viewing your web site. She picked up so many details, most notably how it is like to possess an amazing helping mindset to make the mediocre ones without difficulty learn several advanced things. You undoubtedly exceeded our expected results. I appreciate you for imparting the essential, trustworthy, edifying and even unique tips on the topic to Evelyn.
yeezy boost http://www.yeezy-boost.us.com
引用 游客 2018-2-19 04:39
Would you be fascinated by exchanging hyperlinks?
hermes belt http://www.hermesbelts.com
引用 游客 2018-2-18 04:59
This site is known as a walk-by means of for all of the information you wished about this and didn抰 know who to ask. Glimpse right here, and you抣l positively discover it.
nike air huarache http://www.nike-huarache.com
引用 游客 2018-2-18 00:42
I enjoy you because of every one of your work on this blog. My mother takes pleasure in working on investigation and it is ** to grasp why. My partner and i know all regarding the powerful form you provide rewarding tips and hints by means of your website and as well foster contribution from some others on that point while my daughter is always being taught so much. Have fun with the rest of the new year. You are always carrying out a really good job.
yeezy boost 350 http://tropaadet.dk/yzy ...
引用 游客 2018-2-17 06:54
Spot on with this write-up, I really assume this website needs much more consideration. I抣l most likely be once more to read way more, thanks for that info.
retro jordans http://www.jordan-retro.us.com
引用 游客 2018-2-16 11:30
I needed to draft you a tiny note to help thank you yet again about the stunning ideas you have documented at this time. This is really pretty generous with you to offer unreservedly precisely what many people might have supplied as an e book to generate some money for themselves, specifically now that you might have tried it in the event you decided. Those pointers also served to become a easy way to realize that other people have similar zeal just as my very own to understand more regarding th ...
引用 游客 2018-2-16 10:10
There are certainly quite a lot of details like that to take into consideration. That could be a great level to deliver up. I provide the ideas above as normal inspiration but clearly there are questions like the one you bring up where the most important factor shall be working in sincere good faith. I don?t know if best practices have emerged round issues like that, however I am positive that your job is clearly recognized as a fair game. Each girls and boys really feel the impression of only a ...
引用 游客 2018-2-15 14:40
Would you be thinking about exchanging hyperlinks?
michael kors outlet http://www.michael-kors-handbags.org.uk
引用 游客 2018-2-14 19:36
Spot on with this write-up, I really think this website needs far more consideration. I抣l in all probability be again to read much more, thanks for that info.
yeezy http://www.yeezys.org.uk
引用 游客 2018-2-14 00:04
Spot on with this write-up, I actually suppose this website needs far more consideration. I抣l probably be again to learn far more, thanks for that info.
tory burch shoes http://www.toryburchshoes.us

查看全部评论(87)

返回顶部