原文地址:http://zodiacg.net/2015/08/javascript-async-control-flow/javascript
随着ES6标准逐渐成熟,利用Promise和Generator解决回调地狱问题的话题一直很热门。可是对解决流程控制/回调地狱问题的各类工具认识仍然比较麻烦。最近两天看了不少文章,想出几个场景把各类异步流程方式类比一下,但愿能有助于理解他们的实现。java
须要说明的是类比只能反映被类比的事物的一个方面,必然有其反映不到的部分,不能彻底以类比来理解各类异步控制的本质。因此仅用于简化理解,快速入门,依然须要阅读有深刻研究的文章来加深对各类异步流程控制的方法的掌握。node
文章中没有严格使用Node.js核心模块的函数名,而是伪造的函数名来方便在各类方式下保持一致性和简化书写。git
从最简单的同步开始。以一段经典的代码为例吧,传入文件名,从文件中读取内容并按JSON格式解析,把其中的一部份内容发还给用户。es6
function foo(filename){ var file = readFile(filename); var json = parseJSON(file); return json.someContent; } var resultA=foo('first.json'); var resultB=foo('second.json');
其中readFile
和parseJSON
显而易见是同步、阻塞的函数。github
咱们构造这样一个场景,M(ain)表明着解释引擎的主线程,坐在柜台前等待用户提交请求。web
来了两个用户A和用户B,用户A排在前面。json
用户A: 你好,我要读文件
first.json
。
M:好的你等一下我去拿。
【M离开了柜台去拿文件】
M:好的我回来了,这是你要的first.json
的内容。而后呢?
用户A:而后把内容按JSON解析一下。
M:你等等我去解析一下。
【M离开了柜台去解析文件内容】
M:好了解析完了,而后呢?
用户A:我要里面的someContent
部分。
M:给你。再见。segmentfault用户B:你好,我要读文件
second.json
。
…………promise
能够看到同步阻塞顾名思义,操做一步一步进行,遇到IO操做每一步都要等待,用户A不处理完,用户B也进不来。
仍然是这个需求,此次的代码就比较像Node.js里的常见形式了,此处为了简单忽略了err。因为回调套回调,缩进如同金字塔,被称做回调地狱。固然回调地狱远不是一个缩进金字塔那么简单。
function foo(filename, cb){ readFile(filename,function(file){ parseJSON(file,function(json){ cb(json.someContent); }); }); } foo('first.json',cbA); foo('second.json',cbB);
依然是来了两个用户A和用户B
用户A:你好,我要读文件
first.json
,按JSON解析后把里面的someContent
寄往cbA
发回给我。
M:好的。
【M开始写信,信封上写上文件读取处readFile
,拿出一张信纸写到:“读取first.json
,而后内容放进后附信封中寄出”。】
【M又拿出一个信封,写上JSON解析处parseJSON
,信纸上写到“把给你的file
按JSON解析,而后把里面的someContent
放到所附信封里寄出”。】
【M又又拿出一个信封,写上cbA
,而后把这个信封放进了刚才的信封里,又把刚才的信封塞进了第一个信封里。】
【M把鼓鼓囊囊的信封扔到邮箱里就无论了】
M:下一位!
用户B:你好……
写几封信的时间比本身跑出去取文件要快的多。异步操做带来的处理速度提高是显而易见的。
可是为了保证业务流程的衔接,信里面就包含了后续一切须要进行的操做,层层包裹。第一封信寄出,M就既无从得知信走到了何处,也没法控制readFile
和parseJSON
是否是如本身所想寄出了给他的信封,有没有私自复印了多寄了一两封。
这才是回调地狱真正危险的地方,缺少控制。
引入Promise以后,不少人就觉得能解决回调地狱了,其实否则。在某些场景下只是让缩进好看了一点而已。有些场景下缩进也无法好看,须要书写的回调不只不会减小还会增多。
function foo(filename,cb){ readFile(filename) .then(parseJSON(file)) .then(function(json){ cb(json.someContent)}); } foo('first.json',cbA); foo('second.json',cbB);
固然这里面的readFile
和parseJSON
已是Promise化了的。
依然是来了两个用户A和用户B
用户A:你好,我要读文件
first.json
,按JSON解析后把里面的someContent
寄往cbA
发回给我。
M:好的。
【M叫来了一个办事员小P】
M:小P你听好,先去找readFile
读取first.json
,而后把内容给parseJSON
让他解析一下,最后把解析的内容里的someContent
寄给cb
,懂了吗?
P:我办事你放心!
【小P离开了柜台】
M:下一位!
用户B:你好……
小P是一位M信得过的办事员,M相信他可以挨个去找该找的部门,不偷工减料也不毛手毛脚。让小P去办事比寄一封信靠谱的多,M依然能很快回过头来继续应付下一个用户请求。
固然实际场景中虽然写的时候.then
一会儿连起来写完,并非真的一会儿把内容都交给同一位小P/同一个Promise。更像是一个Promise公司,每一个操做进行完后都由一位Promise公司的办事员进行下一步操做。
Promise物如其名,使用Promise重要的就是Promise的可信性,好比Promise的状态不可逆,好比fulfill
回调只会被调用一次。Promise并非回避书写回调,而是用一种更可靠的方式来书写回调。
这里只谈co不谈Generator,是由于Generator并非为解决异步流程控制而生的,而TJ大神用co把Generator和Thunk/Promise结合在一块儿提供了新的异步流程控制的方法。
var foo=co(function*(filename){ var file = yield readFile(filename); var json = yield parseJSON(file); return json.someContent; }); foo('first.json').then(cbA); foo('second.json').then(cbB);
咦?这代码看起来跟同步的怎么差很少。
此次咱们换个方法描述,同样是来了用户A和用户B,可是先从用户A的视角来看这件事情。
用户A: 你好,我要读文件
first.json
。
M:好的你等一下我去拿。
【M离开了柜台】
M:我回来了,这是你要的first.json
的内容。而后呢?
用户A:而后把内容按JSON解析一下。
M:你等等我去解析一下。
【M离开了柜台】
M:好了解析完了,而后呢?
用户A:我要里面的someContent
部分。
M:给你。再见。
是否是看起来跟同步如出一辙?实际上从M的角度看这件事情呢?
用户A: 你好,我要读文件
first.json
。
M:好的你等一下我去拿。
【M离开了柜台】
M:小P来一下!去readFile
读first.json
,回来叫我。
P:好的我这就去。
【M转向了另外一个柜台窗口】
M:你好。
用户B:你好,我要读文件second.json
。
…………
…………
P:M,first.json
拿回来了。
【M转向第一个柜台】
M:我回来了,这是你要的first.json
的内容。而后呢?
用户A:而后把内容按JSON解析一下。
M:你等等我去解析一下。
【M离开了柜台】
M:小P来一下!去parseJSON
把这堆东西解析一下,回来叫我。
P:好的我这就去。
【M转向了另外一个柜台窗口】
…………
…………
真相大白了,M并无亲自去拿文件解析JSON,而是叫来了不辞辛苦的小P干活,本身在用户A面前假装成被占用了全部的时间的样子,其实偷偷去接待别的用户了。
利用Generator能够用yield
中断执行,再在外部经过next
唤醒继续执行的特性,co把Generator的next
写到Promise的then
里面从而实现循环调用。使用了co以后,代码看起来跟同步很是相像,写起来符合人正常的同步思惟,甚至可使用同步的流程控制语句好比for。可是执行起来却能充分利用异步带来的性能优点。
顺便提一句,co看起来已经很是像async/await方式了。Node.js中一样近似于async/await方式的还有asyncawait库,它不依赖generator而是依赖于node-fiber,看名字大概就是Node里的一个纤程的实现吧。因为不须要generator,对于诸如Coffescript和Typescript类的语言支持很是好。
以上就是对Javascript中最近常讨论的几种异步流程控制的简单类比说明。这种理解方式很是粗浅,并且有不少问题并不像上面写的那样那么简单。要想使用好异步,仍是要多读一些更为深刻的文章。