说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

细说JavaScript异步函数发展历程

HTML文档下载 WORD文档下载 PDF文档下载
对大部分JavaScript开发者而言,async函数仍是新鲜事物,其发展经历了漫长的旅程。本文将梳理总结JavaScript异步函数的发展历程,并表示未来async函数将成为实现异步的主要方式。

《The Evolution of Asynchronous JavaScript》外文梳理了JavaScript异步函数的发展历程,首先通过回调函数实现异步,之后又经历了Promise/A+、生成器函数,而未来将是async函数的。感谢景庄对该文章的翻译,内容如下:

对大部分的JavaScript开发者而言,async函数是个新鲜事物,它的发展经历了一个漫长的旅程。因此本文试图梳理总结JavaScript异步函数的发展历程:在不久之前,我们还只能写回调函数来实现异步,然后Promise/A+标准出来了,这之后又出现了生成器函数,而未来显然是async函数的。

现在让我们一起来回顾这些年来JavaScript异步函数的发展历程吧。

回调函数Callbacks

似乎一切应该从回调函数开始谈起。

异步JavaScript

正如我们所知道的那样,在JavaScript中,异步编程方式只能通过JavaScript语言中的一等公民函数才能完成:这种方式意味着我们可以将一个函数作为另一个函数的参数,在这个函数的内部可以调用被传递进来的函数(即回调函数)。这也正是回调函数诞生的原因:如果你将一个函数作为参数传递给另一个函数(此时它被称为高阶函数),那么在函数内部, 你可以调用这个函数来完成相应的任务。回调函数没有返回值(不要试图用return),仅仅被用来在函数内部执行某些动作。 看一个例子:

Something.save(function(err) {    if (err)  {    //error handling    return; // 没有返回值  }  console.log('success');});

上面的例子中我们演示了一个错误优先的回调函数(error-first callbacks),这也是Node.js本身的特点之一, Node.js中所有的核心模块和NPM仓库中的大部分模块在编写时都会遵循这个特点。

过度使用回调函数所会遇到的挑战:

  • 如果不能合理的组织代码,非常容易造成回调地狱(callback hell),这会使得你的代码很难被别人所理解。
  • 很容易遗漏错误处理代码。
  • 无法使用return语句返回值,并且也不能使用throw关键字。

也正是基于这些原因,在JavaScript世界中,一直都在寻找着能够让异步JavaScript开发变得更简单的可行的方案。

一个可行的解决方案之一是async模块。如果你和回调函数打过很久的交道, 你也许会深刻地感受到,在JavaScript中如果想要让某些事并行执行,或是串行执行,甚至是使用异步函数来映射(mapping) 数组中的元素使用异步函数有多复杂。所以,感谢 Caolan McMahon写了async模块来解决这些问题。

使用async模块,你可以轻松地以下面这种方式编写代码:

async.map([1, 2, 3], AsyncSquaringLibrary.square,    function(err, result){  // result will be [1, 4, 9]});

async模块虽然一定程度上带来了便利,但仍然不够简单,代码也不容易阅读,因此Promise出现了。

Promise

当前的JavaScript异步标准可以追溯到2012年,并且直到ES6才变得可用,然而,Promise这个术语却并不是JavaScript 社区所发明的。这个术语来来自于 Daniel P.friedman在1976年的发表的一篇文章。

一个Promise代表的是一个异步操作的最终结果。

现在我们使用Promise来完成上面代码所完成的任务,Promise风格的代码如下:

Something.save()    .then(function() {    console.log('success');  })  .catch(function() {    //error handling  })

你会发现,Promise中也利用了回调函数。在then和catch方法中都传入了一个回调函数,分别在Promise被满足和被拒绝时执行。Promise函数的另一个优点是它能够被链接起来完成一系列任务。例如,你可以这样写代码:

saveSomething()    .then(updateOtherthing)  .then(deleteStuff)    .then(logResults);

当你没有现成的Promise时,你可能需要借助一些Promise库,一个流行的选择是使用 bluebird。 这些库可能会提供比原生方案更多的功能,并且不局限于Promise/A+标准所规定的特性。

但是你为什么不用糖方法(sugar methods)呢?建议你首先阅读 Promise: The Extension Problem这篇文章。更多关于Promise的信息,可以参考 Promise/A+标准。

你可能会问:如果大部分的库只暴露了回调的接口的话,那么我该如何使用Promise?

嗯,这个很简单,此时你唯一需要做的就是使用Promise来包裹含有回调的那个函数调用体。例如:

回调风格的代码可能是这样的:

function saveToTheDb(value) {    db.values.insert(value, function (err, user) {        if (err) throw err;        // todo: insert user to db    });}

现在我们将其改成支持Promise风格调用的代码:

function saveToTheDb(value) {      return new Promise(function(resolve, reject) {        db.values.insert(value, function(err, user) { // remember error first ;)            if (err) {                return reject(err); // don't forget to return here            }            resolve(user);        })    }}

已经有相当一部分的库或框架同时支持者两种方式了,即同时提供了回调风格和Promise风格的API接口。那么现在, 如果你也想对外提供一个库,最佳实践也是同时提供两种方式的接口。你可以轻松的使用如下方式来达到这个目的:

function foo(cb) {    if (cb) {    return cb();  }  return new Promise(function (resolve, reject) {  });}

或者更简单些,你可以从只提供Promise风格的接口开始后,并使用诸如 callbackify这样的工具来达到向后兼容的目的。其实Callbackify所做的工作和上面的代码片段类似,但在实现上使用了一个更通用的方法, 我建议你可以去阅读Callbackify的源代码。

生成器Generators/ yield

JavaScript 生成器是个相对较新的概念, 它是ES6(也被称为ES2015)的新特性。想象下面这样的一个场景:

当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。

上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。

function* foo () {    var index = 0;  while (index < 2) {    yield index++; //暂停函数执行,并执行yield后的操作  }}var bar =  foo(); // 返回的其实是一个迭代器console.log(bar.next());    // { value: 0, done: false }  console.log(bar.next());    // { value: 1, done: false }  console.log(bar.next());    // { value: undefined, done: true }  

更进一步的,如果你想更轻松的使用生成器函数来编写异步JavaScript代码,我们可以使用 co 这个库,co是著名的tj大神写的。

Co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。

使用co,前面的示例代码,我们可以使用下面的代码来改写:

co(function* (){    yield Something.save();}).then(function() {  // success}).catch(function(err) {  //error handling});

你可能会问:如何实现并行操作呢?答案可能比你想象的简单,如下(其实它就是Promise.all而已):

yield [Something.save(), Otherthing.save()];  

Async/ await

在ES7(还未正式标准化)中引入了Async函数的概念,目前如果你想要使用的话,只能借助于babel 这样的语法转换器将其转为ES5代码。(提醒一点:我们现在讨论的是async关键字,而不是NPM中的async包)。

简而言之,使用async关键字,你可以轻松地达成之前使用生成器和co函数所做到的工作。当然,除了hack之外。

也许你会问,是否在ES7中有了async关键字,yield就变得不是那么重要了?

实际上,使用yield实现异步也不过是一种hack罢了,yield意味着懒次序(lazy sequences)和迭代器。 而await能够完美的分离这两点,首先让yield用于其最初的目的,其次使用await来执行异步操作。

在这背后,async函数实际使用的是Promise,也就是为什么async函数会返回一个Promise的原因。

因此,我们使用async函数来完成类似于前面代码所完成的工作,可以使用下面这样的方式来重新编写代码:

async function save(Something) {    try {    await Something.save(); // 等待await后面的代码执行完,类似于yield  } catch (ex) {    //error handling  }  console.log('success');} 

正如你看到的那样,使用async函数,你需要在函数声明的最前面加上async关键字。这之后,你可以在函数内部使用await关键字了,作用和之前的yield作用是类似的。

使用async函数完成并行任务与yiled的方式非常的相似,唯一不同的是,此时Promise.all不再是隐式的,你需要显示的调用它:

async function save(Something) {      await Promise.all[Something.save(), Otherthing.save()]}

Koa也支持async函数,如果你也在使用koa,那么你现在就可以借助babel使用这一特性了。

import koa from koa;  let app = koa();app.experimental = true;app.use(async function (){      this.body = await Promise.resolve('Hello Reader!')})app.listen(3000);  

拓展阅读

Hapi with generators

Koa

(责编:陈秋歌)

原文链接:The Evolution of Asynchronous JavaScript

译者简介:景庄,前端工程师,关注Node.js、前端工程化。个人博客: http://wwsun.github.com。

欢迎加入CSDN前端交流群2:465281214,进行前端技术交流。  

【CTO俱乐部第92期】大规模存储与电商架构演化实战分享 刘黎明:PaaS是非主流业务,要与IaaS融合 个人手机游戏开发者之死 为什么苹果的应用审核是个笑话? 程序员性格怪癖是才华横溢的表现,还是危险分子的征兆? JavaScript Source Map详解 8条学习编程的可靠建议 一周消息树:亲爱的GitHub,我们怎么访问不了你? [回顾] 2012年几大编程语言就业趋势分析 研发周报:微软无视W3C 拒绝遵循WebRTC标准 移动周报:夫妻档闯江湖 事业爱情双丰收 当服务器遇见锤子:第六届AWS全球创业大赛落幕 C# DataTable 和List之间相互转换的方法 iPad如何在三年内改变世界 划时代的成果:新DNA存储技术 轻而易举装下世界 谷歌产品设计:Larry Page的魅力革命 开源移动统计工具Countly更新 支持Unity 3D DailyCost传奇设计师访谈,彰显HTML5移动应用优势 如何成为一名架构师? Apache Hadoop集群的安全性架构 Wood Camera开发者访谈:我们如何冲到榜首的 代码审查:程序员内炼之道 Twitter Q1推广告API 收入剑指10亿 Dell:在OpenStack成熟后才会推出服务 一个开源工作者对开源与赚钱的一些想法 宏碁:Windows8仍不成功 Chrome表现令人意外 雅虎发布Q4财报 净利润同比下滑8% 游戏机禁售令起风波,索尼任天堂股价大涨 国外开发者吐槽Facebook Android设计一例 Rovio转向发行业务 将发布新作Tiny Thief 上山下海 “硬汉”服务器无所不能 请问那里可以下载到delphi6 哪里有〈〈C++习题与解析〉〉李春葆 电子版下载???? 这个存储过程怎么写? VBS问题!如何取得数字签名。 对PCM WAV BUFFER 压缩成MP3格式的BUFFER,如果将这个压缩后的BUFFER写入一个文件中生成.mp3文件,要如何生成文件头? scjp一道题! 请问如何在计算机启动时自动运行我的程序呢? 我想问一个关于打开网页速度的问题 动态生成多个控件 右键单击ClassView(类视图)中的类名,自动退出IDE。 请问如何去除Bitmap的背景?(79分) 请教如何实现用quickrep显示dbgrid中的内容? 如果存储变量使用过多的参数的话,是不是就会得不到提高速度的效果了?? 我的linux可以访问操作系统是98的机器,但不能访问w2k,why??? 超高技术难题,手写的数据窗口数据源代码问题 哪里有source inside,或者是source navigator 谁知道ISO8583的标准 对象,实例,reference有什么区别吗 程序员有时间交女朋友吗????????? 怎样做才能使后面的子程序在前面子程序已经画好的图形里添加画线? 哪里有VC6的英文版下载呀,市场上买不到英文的呀 大家说说看,vc连接access的应用程序,用什么做显示数据好。 有没有雷锋?! ccnuxjg(阿木) 各位高人,我GG考我一个谜语,限我今天答复,请大家帮帮我。(四字成语) 怎样删除重复的行(mssql) 一个页面,如何实现左边为链接信息,右边为内容? 关于模式对话框的问题 项目实战两问,关于操作系统和光盘运行,高手请进! 怎样判断发信成功 CDONTS.NewMail 在杭州搞JSP的,请进来交换QQ,邮件!我们需要交流! 如何用sendmessage让一个窗口最小化 我想把COM+组件的客户端安装包打到install shield 的安装包中,并且自动安装com+客户端安装包,可是。。。。 word2000中不能使用打印功能,为什么? CSDN是怎么了,三天两头出问题啊~!!!!!!!!!!!!!!!!!!!!!!!!!!!!! OleContainer.SaveToFile的文件用Word怎么打不开? vb的多线程如何实现? 这句话是什么意思!!!! 有个女孩问我的代码理论问题……紧急!!有请各路大仙 指针与数组常通用,但一个存值,一个存地址,能不能讲一下他们的内存如何分配,怎样互用的 我一和女朋友有矛盾时,就感到浑身无力,什么也做不下去,脑袋乱七八糟的,做什么都不专一,直到问题解决,不知大家有没有和我有一样感觉的? 海星是南京大学的吗? 请问。vb有没有字体选择的控件(.ocx) 请问在该专区里有没有四川绵阳水校到深圳的王师兄!huan_91_cn@china.com 游戏爱好狂们注意:有没有愿意建立游戏网页!!! 各位大虾请帮忙!!谢谢!!!!! 有关数据报表的处理,采用EXCEL 听说所有Enterprise Manager工具都允许简单地通过单击鼠标来切换身份 怎么恢复? 在数据库中建立临时表来进行数据操作,我对它在使用时何时建立何时删除不太了解,能给我说明一下吗? 如何在触发器中调用存储过程(sql server 或者 oracle)(无内容) 刀具实物鉴定下 做这种刀具要用什么钢材?刀具实物 定性结果如下C碳 0.96 S 硫0.001MN锰 0.30SI硅 0.23Po 0.007NI 0.099Cr 0.46Mo 0.022V 0.18Cu 0.092Al 0.015用什么板材?什么品种的 相近的也可以. 王庄要修一条长1200米的公路,前三天完成了40%,照这样计算,修这条路一共要用多少天? 切削用量的选择原则是什么 如图,电源电压U=18V不变,小灯泡L的额定电压为9V,闭合开关S,当滑动变阻器R的滑片P移到中点时,小灯泡正常发光,当P移到最右端时,小灯泡实际功率为4W,忽略灯丝电阻变化,则小灯泡额定功率为多 如图所示,L是标有“12v 6w”的灯泡,R是一个滑动变阻器,A、B间的电压U为18V.当滑动变阻器的滑片P移至c�如图所示,L是标有“12v 6w”的灯泡,R是一个滑动变阻器,A、B间的电压U为18V.当滑动变阻器 橡胶硬度怎么标记如果说用邵氏硬度计测量某橡胶的硬度为40度,应该怎么标记?还是直接写成那样就可以了? 电源电压u=18v当滑动变阻器的滑片P一直最左端时,小灯泡l正常发光,电流表示数为0.5A;当滑片P移至中点时,电流表示数变为0.25A求滑动变阻器的最大阻值【小灯泡与L、R串联的】 刀具的基本知识 橡胶板的硬度问题橡胶板的邵氏硬度在30---40度之间,其软硬程度会是什么样?能不能很具体的很生动地形容一下呀? 塑料为什么会老化塑料放一段时间会变得很脆,这是为什么? 刀具方面的知识解答,专家来45°弯头车刀在车端面和外圆,刀具角度有没有变化?为什么?什么条件前角等于副刀刃刃倾角?主刀刃刃倾角等于副刀刃前角? 电源电动势中dl 什么是塑料的老化现象?塑料的老化的主要表现? 海市蜃楼原理 “电源电动势为1.5V”的意义是什么? 什么是塑料抗张强度 修路队修一条长2400米的公路,如果每天修200米,12天修完,实际3天就修了720米.照这样计算,这条公路几天修完?(用正、反比例两种方法解答) 下列对电源电动势概念的认识中,正确的是789 立方氮化硼刀具表面如何达到镜面或是用什么工艺抛光? 塑料老化后产生什么生活中经常见到塑料薄膜长时间在阳光下暴晒会老化老化后塑料会出现变色、变薄、变脆,甚至消失请问老化消失的塑料转化成了什么?对人有害么?以及是否可以利用这个 修路队修一条长2400米的路,如果每天修200米,12天修完,实际3天就修了720米.照这样计算, 修一条1800米长的公路,6天修了全长的8分之3,照这样的速度,还需要几天才能修完? 塑料老化后,为什么力学性能会降低? 海市蜃楼产生原理 一个工程队修一条1200m的公路,6天修了全长的3/8,照这样的速度,修完这条公路还要几天?(用两种方法解答 金属铬是如何生产的 海市蜃楼和山市形成的科学原理快,帮我个忙. 什么是变压器的电压调整率,它与哪些因素有关? 金属铬是什么铬的作用, 海市蜃楼是怎样形成的,请注明原理. 变压器有哪些调压方式?电压调节范围是多少?怎怎样调压? 金属铬是什么颜色 关于物理方面的常识?1.什么是同步通信卫星?它的主要作用是什么?2.为什么收音机在晚上能收听到较多外地电台的广播?3.激光通信和微波通信相比较,有什么主要的优势?光导纤维的作用是什么? 变压器输出的是瞬时电压吗?那U1、U2是有效值啊,有效值不是固定的吗, 金属铬粉是什么?有何用途? 在橡胶检测机构测定橡胶的硬度,拉伸强度,伸长率,密度这四项指标的价格大约是多少请诸位测定过橡胶的前辈们告诉告诉我测定橡胶的硬度、拉伸强度、伸长率,密度这四项指标的价格是多少 海市蜃楼的景象简要描述及其科学道理 UASB反应器起什么作用及特点是什么 修路队修一条长2400米的路,如果每天修200米,12天修完,实际3天就修了720米.照这样计算,这条路几天修完?(用正反比例两种方法解答)在比例尺是1:6000000得地图上,量得甲乙两地的距离是15厘米. 海市蜃楼的科学道理两三句描述一楼没看到吗? uasb反应器对废水的有机负荷有哪些要求 修路队修一条长2400米的路,如果每天修200米.12天正好修完,实际3天就修了720米.照这样计算,这条路需几天修完?(方程解.用正、反比例两种方法解答) “海市蜃楼”在现代人的心中,已不再仅仅是一种科学现象,他的新的含义是什么 李庄要修一条1200米的路,前3天完成了40%,照此计算,修完这条路还需多少天 电动势,电压,电势差的物理意义相同吗? 天然橡胶 硬度 60 拉伸强度 15 扯断伸长率 450 怎样配置 合多少钱一斤 UASB反应器污水停留时间60小时可以吗; 动生电动势与感生电动势的物理意义分别是什么? 什么是拉伸强度? 王庄要修一条1200米的路道,3天完成了40%,照这样计算,修这条路一共要多少天? 关于物理电动势里的一些公式(1)已知E,U内,r,则R=?(2)已知I,R和U内,则E=?U外=?r=? 天然橡胶通过什么措施可以增大强度和弹性RT 修路队修一条1200米的路,前3天完成40%,照这样计算,还要多少天才能修完这条路? (1)某同学在研究性学习过程中想通过上述电压表测量一个多用表中欧姆档的内部电源的 电动势,他们从多用表刻度盘上读出电阻刻度中间值为30. A.请你将他们的实验电路连接起来.他们 剪刀是用什么金属做的 李庄要修筑一条长1200米的道路,前3天完成了百分之四十,照这样计算,修筑这条路一共要用多少天? 电动势,电压,电势差的物理意义分别是什么? 剪刀利用了金属的什么性质 海市蜃楼的原理是什么?海市蜃楼的结构与原理是什么? 选择切削用量的基本原则是什么?
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn