异步机制,异步处理机制

2020-10-12 阅读 杨蓓莉整理

内容简介:异步本文主要介绍对JavaScript编程中同步和异步机制的深入理解。不仅Ajax已经渗透到了各个角落,而且node.js的流行也使得js异步编程特别具有吸引力。可异步任务处理机制理解...

这篇文章主要介绍了深入理解JavaScript编程中的同步与异步机制,不仅仅是AJAX已经深入到了各个角落,Node.js的火爆也让JS的异步编程格外引人注目,需要的朋友可以参考下JavaScript的优势之一是其如何处理异步代码。

异步代码会被放入一个事件队列,等到所有其他代码执行后才进行,而不会阻塞线程。

然而,对于初学者来说,书写异步代码可能会比较困难。

而在这篇文章里,我将会消除你可能会有的任何困惑。

理解异步代码JavaScript最基础的异步函数是setTimeout和setInterval。

setTimeout会在一定时间后执行给定的函数。

它接受一个回调函数作为第一参数和一个毫秒时间作为第二参数。

以下是用法举例:?1234567891011console.log(a);setTimeout(function(){console.log(c)},500);setTimeout(function(){console.log(d)},500);setTimeout(function(){console.log(e)},500);console.log(b);正如预期,控制台先输出a、b,大约500毫秒后,再看到c、d、e。

我用大约是因为setTimeout事实上是不可预知的。

实际上,甚至HTML5规范都提到了这个问题:这个API不能保证计时会如期准确地运行。

由于CPU负载、其他任务等所导致的延迟是可以预料到的。

有趣的是,直到在同一程序段中所有其余的代码执行结束后,超时才会发生。

所以如果设置了超时,同时执行了需长时间运行的函数,那么在该函数执行完成之前,超时甚至都不会启动。

实际上,异步函数,如setTimeout和setInterval,被压入了称之为EventLoop的队列。

EventLoop是一个回调函数队列。

当异步函数执行时,回调函数会被压入这个队列。

JavaScript引擎直到异步函数执行完成后,才会开始处理事件循环。

这意味着JavaScript代码不是多线程的,即使表现的行为相似。

事件循环是一个先进先出(FIFO)队列,这说明回调是按照它们被加入队列的顺序执行的。

JavaScript被node选做为开发语言,就是因为写这样的代码多么简单啊。

Ajax异步Javascript与XML(AJAX)永久性的改变了Javascript语言的状况。

突然间,浏览器不再需要重新加载即可更新web页面。

在不同的浏览器中实现Ajax的代码可能漫长并且乏味;但是,幸亏有jQuery(还有其他库)的帮助,我们能够以很容易并且优雅的方式实现客户端-服务器端通讯。

我们可以使用jQuery跨浏览器接口$.ajax很容易地检索数据,然而却不能呈现幕后发生了什么。

比如:?12345678910vardata;$.ajax({url:some/url/1,success:function(data){//But,thiswill!console.log(data);}})//Oops,thiswon'twork...console.log(data);较容易犯的错误,是在调用$.ajax之后马上使用data,但是实际上是这样的:?1234567xmlhttp.open(GET,some/ur/1,true);xmlhttp.onreadystatechange=function(data){if(xmlhttp.readyState===4){console.log(data);}};xmlhttp.send(null);底层的XmlHttpRequest对象发起请求,设置回调函数用来处理XHR的readystatechnage事件。

然后执行XHR的send方法。

在XHR运行中,当其属性readyState改变时readystatechange事件就会被触发,只有在XHR从远端服务器接收响应结束时回调函数才会触发执行。

处理异步代码异步编程很容易陷入我们常说的回调地狱。

因为事实上几乎JS中的所有异步函数都用到了回调,连续执行几个异步函数的结果就是层层嵌套的回调函数以及随之而来的复杂代码。

node.js中的许多函数也是异步的。

因此如下的代码基本上很常见:?12345678910varfs=require(fs);fs.exists(index.js,function(){fs.readFile(index.js,utf8,function(err,contents){contents=someFunction(contents);//dosomethingwithcontentsfs.writeFile(index.js,utf8,function(){console.log(whew!Donefinally...);});});});console.log(executing...);下面的客户端代码也很多见:?1234567891011121314151617181920212223242526GMaps.geocode({address:fromAddress,callback:function(results,status){if(status==OK){fromLatLng=results[0].geometry.location;GMaps.geocode({address:toAddress,callback:function(results,status){if(status==OK){toLatLng=results[0].geometry.location;map.getRoutes({origin:[fromLatLng.lat(),fromLatLng.lng()],destination:[toLatLng.lat(),toLatLng.lng()],travelMode:driving,unitSystem:imperial,callback:function(e){console.log(ANNNNDFINALLYhere'sthedirections...);//dosomethingwithe}});}}});}}});Nestedcallbackscangetreallynasty,butthereareseveralsolutionstothisstyleofcoding.嵌套的回调很容易带来代码中的坏味道,不过你可以用以下的几种风格来尝试解决这个问题Theproblemisn'twiththelanguageitself;it'swiththewayprogrammersusethelanguageAsyncJavascript.没有糟糕的语言,只有糟糕的程序猿异步JavaSript命名函数清除嵌套回调的一个便捷的解决方案是简单的避免双层以上的嵌套。

传递一个命名函数给作为回调参数,而不是传递匿名函数:?123456789101112131415161718192021222324252627282930varfromLatLng,toLatLng;varrouteDone=function(e){console.log(ANNNNDFINALLYhere'sthedirections...);//dosomethingwithe};vartoAddressDone=function(results,status){if(status==OK){toLatLng=results[0].geometry.location;map.getRoutes({origin:[fromLatLng.lat(),fromLatLng.lng()],destination:[toLatLng.lat(),toLatLng.lng()],travelMode:driving,unitSystem:imperial,callback:routeDone});}};varfromAddressDone=function(results,status){if(status==OK){fromLatLng=results[0].geometry.location;GMaps.geocode({address:toAddress,callback:toAddressDone});}};GMaps.geocode({address:fromAddress,callback:fromAddressDone});此外,async.js库可以帮助我们处理多重Ajaxrequests/responses.例如:?1234567891011121314151617181920async.parallel([function(done){GMaps.geocode({address:toAddress,callback:function(result){done(null,result);}});},function(done){GMaps.geocode({address:fromAddress,callback:function(result){done(null,result);}});}],function(errors,results){getRoute(results[0],results[1]);});这段代码执行两个异步函数,每个函数都接收一个名为done的回调函数并在函数结束的时候调用它。

当两个done回调函数结束后,parallel函数的回调函数被调用并执行或处理这两个异步函数产生的结果或错误。

Promises模型引自CommonJS/A:promise表示一个操作独立完成后返回的最终结果。

有很多库都包含了promise模型,其中jQuery已经有了一个可使用且很出色的promiseAPI。

jQuery在1.5版本引入了Deferred对象,并可以在返回promise的函数中使用jQuery.Deferred的构造结果。

而返回promise的函数则用于执行某种异步操作并解决完成后的延迟。

?123456789101112131415161718192021222324252627282930vargeocode=function(address){vardfd=new$.Deferred();GMaps.geocode({address:address,callback:function(response,status){returndfd.resolve(response);}});returndfd.promise();};vargetRoute=function(fromLatLng,toLatLng){vardfd=new$.Deferred();map.getRoutes({origin:[fromLatLng.lat(),fromLatLng.lng()],destination:[toLatLng.lat(),toLatLng.lng()],travelMode:driving,unitSystem:imperial,callback:function(e){returndfd.resolve(e);}});returndfd.promise();};vardoSomethingCoolWithDirections=function(route){//dosomethingwithroute};$.when(geocode(fromAddress),geocode(toAddress)).then(function(fromLatLng,toLatLng){getRoute(fromLatLng,toLatLng).then(doSomethingCoolWithDirections);});这允许你执行两个异步函数后,等待它们的结果,之后再用先前两个调用的结果来执行另外一个函数。

promise表示一个操作独立完成后返回的最终结果。

在这段代码里,geocode方法执行了两次并返回了一个promise。

异步函数之后执行,并在其回调里调用了resolve。

然后,一旦两次调用resolve完成,then将会执行,其接收了之前两次调用geocode的返回结果。

结果之后被传入getRoute,此方法也返回一个promise。

最终,当getRoute的promise解决后,doSomethingCoolWithDirections回调就执行了。

事件事件是另一种当异步回调完成处理后的通讯方式。

一个对象可以成为发射器并派发事件,而另外的对象则监听这些事件。

这种类型的事件处理方式称之为观察者模式。

backbone.js库在withBackbone.Events中就创建了这样的功能模块。

?123456789101112131415varSomeModel=Backbone.Model.extend({url:/someurl});varSomeView=Backbone.View.extend({initialize:function(){this.model.on(reset,this.render,this);this.model.fetch();},render:function(data){//dosomethingwithdata}});varview=newSomeView({model:newSomeModel()});还有其他用于发射事件的混合例子和函数库,例如jQueryEventEmitter,EventEmitter,monologue.js,以及node.js内建的EventEmitter模块。

事件循环是一个回调函数的队列。

一个类似的派发消息的方式称为中介者模式,postal.js库中用的即是这种方式。

在中介者模式,有一个用于所有对象监听和派发事件的中间人。

在这种模式下,一个对象不与另外的对象产生直接联系,从而使得对象间都互相分离。

绝不要返回promise到一个公用的API。

这不仅关系到了API用户对promises的使用,也使得重构更加困难。

不过,内部用途的promises和外部接口的事件的结合,却可以让应用更低耦合且便于测试。

在先前的例子里面,doSomethingCoolWithDirections回调函数在两个geocode函数完成后执行。

然后,doSomethingCoolWithDirections才会获得从getRoute接收到的响应,再将其作为消息发送出去。

?12345vardoSomethingCoolWithDirections=function(route){postal.channel(ui).publish(directions.done,{route:route});};这允许了应用的其他部分不需要直接引用产生请求的对象,就可以响应异步回调。

而在取得命令时,很可能页面的好多区域都需要更新。

在一个典型的jQueryAjax过程中,当接收到的命令变化时,要顺利的回调可能就得做相应的调整了。

这可能会使得代码难以维护,但通过使用消息,处理UI多个区域的更新就会简单得多了。

?12345678varUI=function(){this.channel=postal.channel(ui);this.channel.subscribe(directions.done,this.updateDirections).withContext(this);};UI.prototype.updateDirections=function(data){//Therouteisavailableondata.route,nowjustupdatetheUI};app.ui=newUI();另外一些基于中介者模式传送消息的库有amplify,PubSubJS,andradio.js。

结论JavaScript使得编写异步代码很容易.使用promises,事件,或者命名函数来避免callbackhell.为获取更多javascript异步编程信息,请点击AsyncJavaScript:BuildMoreResponsiveAppswithLess.更多的实例托管在github上,地址NetTutsAsyncJS,赶快Clone吧!

作者给您推荐的内容
  1. 很多人是通过手机登录微信的,那么电脑上可不可以使用微信呢?01、答案是可以的,而且又两种登录方式呢,首先就是直接通过微信网页版来登录,通过搜狗搜索“微信网页版”找到官网,如下图...

  2. win7还原更改怎么跳过当我们使用win7时,电脑右下角经常会有关于windows更新的提示,强迫症很烦人。windows更新意味着什么?我能把它关掉吗?在Windows 7中win7配置更新100%不动...

  3. I3-6100带显卡推荐I3-6100带什么显卡?对于DIY安装,除了CPU和主板搭配是特殊的,显卡也是i3-6100搭配显卡推荐很多玩家考虑的主要因素。I3-6100是英特尔去年推出的基于14nm的新一代6代产品...

  4. 在写论文的时候为了方便查找,往往需要用到目录,下面教大家word文档目录怎样自动生成。01、首先找到我们需要自动生成目录的word文档打开。02、然后在打开的word文档中点击【视图】。03、接着...

  5. 显示器亮度对比度设置现在电脑很普遍,很多家庭都有,电脑是一个庞大的知识库,我们可以学到很多东西,甚至小孩也会用电脑,有些用户觉得笔记本电脑屏幕的色彩质量不是很好,...

  6. 今天小编要和大家分享的是苹果手机忘记开机密码怎么办,希望能够帮助到大家。01、首先在我们的朋友苹果手机上找到查找iPhone并点击它,如下图所示。02、然后输入我们的账号密码并点击登录,...

  7. 免费桌面管理软件我们的日常工作和生活都离不开电脑的使用,但是桌面一天比一天乱。厌倦了桌面的杂乱,想让桌面更简洁易用,那就用一些好的桌面管理软件来管理你的电脑桌面快...

  8. 现在大家的手机上都会有视频软件,使用的时候难免会遇到需要切换账号的情况。那么腾讯视频微信登录怎么切换其他微信账号登录呢,一起来看一下。01、首先我们打开腾讯视频,然后点击页面底...

  9. 指令由什么组成操作指令不同于编译指令。编译指令通知servlet引擎处理消息,而操作指令只是运行时的脚本操作。编译指令在将JSP编译成servlet时起作用:处理指令通常可以指令是由什么...

  10. 你试过改变默认的改变windows7系统默认声音的方法Windows声音吗?如果不是,所有版本的windows应该是相同的。现在,让我来谈谈如何更改Windows7的默认声音。我安装系统的第一件事就是删除默认声音...