资讯专栏INFORMATION COLUMN

简单理解Javascript的各种异步流程控制方法

makeFoxPlay / 2353人阅读

摘要:所以仅用于简化理解,快速入门,依然需要阅读有深入研究的文章来加深对各种异步流程控制的方法的掌握。

原文地址:http://zodiacg.net/2015/08/javascript-async-control-flow/

随着ES6标准逐渐成熟,利用Promise和Generator解决回调地狱问题的话题一直很热门。但是对解决流程控制/回调地狱问题的各种工具认识仍然比较麻烦。最近两天看了很多文章,想出几个场景把各种异步流程方式类比一下,希望能有助于理解他们的实现。

需要说明的是类比只能反映被类比的事物的一个方面,必然有其反映不到的部分,不能完全以类比来理解各种异步控制的本质。所以仅用于简化理解,快速入门,依然需要阅读有深入研究的文章来加深对各种异步流程控制的方法的掌握。

文章中没有严格使用Node.js核心模块的函数名,而是伪造的函数名来方便在各种方式下保持一致性和简化书写。

同步

从最简单的同步开始。以一段经典的代码为例吧,传入文件名,从文件中读取内容并按JSON格式解析,把其中的一部分内容发还给用户。

function foo(filename){
    var file = readFile(filename);
    var json = parseJSON(file);
    return json.someContent;
}

var resultA=foo("first.json");
var resultB=foo("second.json");

其中readFileparseJSON显而易见是同步、阻塞的函数。

我们构造这样一个场景,M(ain)代表着解释引擎的主线程,坐在柜台前等待用户提交请求。

来了两个用户A和用户B,用户A排在前面。

用户A: 你好,我要读文件first.json
M:好的你等一下我去拿。
【M离开了柜台去拿文件】
M:好的我回来了,这是你要的first.json的内容。然后呢?
用户A:然后把内容按JSON解析一下。
M:你等等我去解析一下。
【M离开了柜台去解析文件内容】
M:好了解析完了,然后呢?
用户A:我要里面的someContent部分。
M:给你。再见。

用户B:你好,我要读文件second.json
…………

可以看到同步阻塞顾名思义,操作一步一步进行,遇到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就既无从得知信走到了何处,也无法控制readFileparseJSON是不是如自己所想寄出了给他的信封,有没有私自复印了多寄了一两封。

这才是回调地狱真正危险的地方,缺乏控制。

Promise

引入Promise之后,很多人就以为能解决回调地狱了,其实不然。在某些场景下只是让缩进好看了一点而已。有些场景下缩进也没法好看,需要书写的回调不仅不会减少还会增多。

function foo(filename,cb){
    readFile(filename)
    .then(parseJSON(file))
    .then(function(json){
    cb(json.someContent)});
}

foo("first.json",cbA);
foo("second.json",cbB);

当然这里面的readFileparseJSON已经是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)

这里只谈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来一下!去readFilefirst.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中最近常讨论的几种异步流程控制的简单类比说明。这种理解方式非常粗浅,而且有很多问题并不像上面写的那样那么简单。要想使用好异步,还是要多读一些更为深入的文章。

参考资料

Promises and Generators: Control Flow Utopia

Promises/A+

Managing Node.js Callback Hell with Promises, Generators and Other Approaches

Replacing callbacks with ES6 Generators

getiblog: Promises

异步流程控制:7 行代码学会 co 模块

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

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

相关文章

  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • javascript异步编程详解

    摘要:在服务器端,异步模式甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有请求,服务器性能会急剧下降,很快就会失去响应。第三是,捕捉不到他的错误异步编程方法回调函数这是异步编程最基本的方法。 前言 你可能知道,Javascript语言的执行环境是单线程(single thread)。所谓单线程,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面...

    huangjinnan 评论0 收藏0
  • 【全文】狼叔:如何正确学习Node.js

    摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...

    Edison 评论0 收藏0
  • 【全文】狼叔:如何正确学习Node.js

    摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...

    fengxiuping 评论0 收藏0

发表评论

0条评论

makeFoxPlay

|高级讲师

TA的文章

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