这篇文章主要介绍了深入理解JavaScript编程中的同步与异步机制,不仅仅是AJAX已经深入到了各个角落,Node.js的火爆也让 *** 的异步编程格外引人注目,需要的朋友可以参考下 JavaScript的优势之一是其如何处理异步代码。
异步代码会被放入一个事件队列,等到所有其他代码执行后才进行,而不会阻塞线程。
然而,对于初学者来说,书写异步代码可能会比较困难。
而在这篇文章里,我将会消除你可能会有的任何困惑。
理解异步代码 JavaScript最基础的异步函数是setTimeout和setInterval。
setTimeout会在一定时间后执行给定的函数。
它接受一个回调函数作为之一参数和一个毫秒时间作为第二参数。
以下是用法举例: ? 1 2 3 4 5 6 7 8 9 10 11 console.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,被压入了称之为Event Loop的队列。
Event Loop是一个回调函数队列。
当异步函数执行时,回调函数会被压入这个队列。
JavaScript引擎直到异步函数执行完成后,才会开始处理事件循环。
这意味着JavaScript代码不是多线程的,即使表现的行为相似。
事件循环是一个先进先出(FIFO)队列,这说明回调是按照它们被加入队列的顺序执行的。
JavaScript被 node选做为开发语言,就是因为写这样的代码多么简单啊。
Ajax 异步Javascript与XML(AJAX)永久性的改变了Javascript语言的状况。
突然间,浏览器不再需要重新加载即可更新web页面。
在不同的浏览器中实现Ajax的代码可能漫长并且乏味;但是,幸亏有jQuery(还有其他库)的帮助,我们能够以很容易并且优雅的方式实现客户端-服务器端通讯。
我们可以使用jQuery跨浏览器接口$.ajax很容易地检索数据,然而却不能呈现幕后发生了什么。
比如: ? 1 2 3 4 5 6 7 8 9 10 var data; $.ajax({ url: some/url/1, success: function( data ) { // But, this will! console.log( data ); } }) // Oops, this won't work... console.log( data ); 较容易犯的错误,是在调用$.ajax之后马上使用data,但是实际上是这样的: ? 1 2 3 4 5 6 7 xmlhttp.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从远端服务器接收响应结束时回调函数才会触发执行。
处理异步代码 异步编程很容易陷入我们常说的回调地狱。
因为事实上几乎 *** 中的所有异步函数都用到了回调,连续执行几个异步函数的结果就是层层嵌套的回调函数以及随之而来的复杂代码。
node.js中的许多函数也是异步的。
因此如下的代码基本上很常见: ? 1 2 3 4 5 6 7 8 9 10 var fs = require( fs ); fs.exists( index.js, function() { fs.readFile( index.js, utf8, function( err, contents ) { contents = someFunction( contents ); // do something with contents fs.writeFile( index.js, utf8, function() { console.log( whew! Done finally... ); }); }); }); console.log( executing... ); 下面的客户端代码也很多见: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 GMaps.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( ANNNND FINAL *** here's the directions... ); // do something with e } }); } } }); } } }); Nested callbacks can get really nasty, but there are several solutions to this style of coding. 嵌套的回调很容易带来代码中的坏味道,不过你可以用以下的几种风格来尝试解决这个问题 The problem isn't with the language itself; it's with the way programmers use the language Async Javascript. 没有糟糕的语言,只有糟糕的程序猿 异步JavaSript 命名函数 清除嵌套回调的一个便捷的解决方案是简单的避免双层以上的嵌套。
传递一个命名函数给作为回调参数,而不是传递匿名函数: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var fromLatLng, toLatLng; var routeDone = function( e ){ console.log( ANNNND FINAL *** here's the directions... ); // do something with e }; var toAddressDone = 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 }); } }; var fromAddressDone = 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 库可以帮助我们处理多重Ajax requests/responses. 例如: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async.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模型 引自 Common *** /A: promise表示一个操作独立完成后返回的最终结果。
有很多库都包含了promise模型,其中jQuery已经有了一个可使用且很出色的promise API。
jQuery在1.5版本引入了Deferred对象,并可以在返回promise的函数中使用jQuery.Deferred的构造结果。
而返回promise的函数则用于执行某种异步操作并解决完成后的延迟。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var geocode = function( address ) { var dfd = new $.Deferred(); GMaps.geocode({ address: address, callback: function( response, status ) { return dfd.resolve( response ); } }); return dfd.promise(); }; var getRoute = function( fromLatLng, toLatLng ) { var dfd = new $.Deferred(); map.getRoutes({ origin: [ fromLatLng.lat(), fromLatLng.lng() ], destination: [ toLatLng.lat(), toLatLng.lng() ], travelMode: driving, unitSystem: imperial, callback: function( e ) { return dfd.resolve( e ); } }); return dfd.promise(); }; var doSomethingCoolWithDirections = function( route ) { // do something with route }; $.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中就创建了这样的功能模块。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var SomeModel = Backbone.Model.extend({ url: /someurl }); var SomeView = Backbone.View.extend({ initialize: function() { this.model.on( reset, this.render, this ); this.model.fetch(); }, render: function( data ) { // do something with data } }); var view = new SomeView({ model: new SomeModel() }); 还有其他用于发射事件的混合例子和函数库,例如 jQuery Event Emitter , EventEmitter , monologue.js ,以及node.js内建的 EventEmitter 模块。
事件循环是一个回调函数的队列。
一个类似的派发消息的方式称为 中介者模式 , postal.js 库中用的即是这种方式。
在中介者模式,有一个用于所有对象监听和派发事件的中间人。
在这种模式下,一个对象不与另外的对象产生直接联系,从而使得对象间都互相分离。
绝不要返回promise到一个公用的API。
这不仅关系到了API用户对promises的使用,也使得重构更加困难。
不过,内部用途的promises和外部接口的事件的结合,却可以让应用更低耦合且便于测试。
在先前的例子里面,doSomethingCoolWithDirections回调函数在两个geocode函数完成后执行。
然后,doSomethingCoolWithDirections才会获得从getRoute接收到的响应,再将其作为消息发送出去。
? 1 2 3 4 5 var doSomethingCoolWithDirections = function( route ) { postal.channel( ui ).publish( directions.done, { route: route }); }; 这允许了应用的其他部分不需要直接引用产生请求的对象,就可以响应异步回调。
而在取得命令时,很可能页面的好多区域都需要更新。
在一个典型的jQuery Ajax过程中,当接收到的命令变化时,要顺利的回调可能就得做相应的调整了。
这可能会使得代码难以维护,但通过使用消息,处理UI多个区域的更新就会简单得多了。
? 1 2 3 4 5 6 7 8 var UI = function() { this.channel = postal.channel( ui ); this.channel.subscribe( directions.done, this.updateDirections ).withContext( this ); }; UI.prototype.updateDirections = function( data ) { // The route is available on data.route, now just update the UI }; app.ui = new UI(); 另外一些基于中介者模式传送消息的库有 amplify, PubSub *** , and radio.js。
结论 JavaScript 使得编写异步代码很容易. 使用 promises, 事件, 或者命名函数来避免callback hell. 为获取更多javascript异步编程信息,请点击Async JavaScript: Build More Responsive Apps with Less . 更多的实例托管在github上,地址NetTutsAsync *** ,赶快Clone吧 !
$html.= ''; 爆出字段28、1.Burp中Options->Upstream Proxy Servers,装备原Proxy(你本来上网的)。 Ualpha表明26为大写字母经过该符号即...
· 全球最大的虚拟钱银数字财物买卖所服务渠道官方Binance遭受垂钓进犯,很多使用过API量化买卖的用户发现自己在Binance的数字钱银账户被盗,账户持有的钱银未经赞同就被主动出售,出售取得的比特...
我想问一下上海市哪有高端陪游,去靠谱预约陪游企业 在网址上听见有些人想要知道怎样找上海市认证高档平面模特美女并且根据一些商务女学妹的艺人经纪人开展一个很好的预定,让大量的人都掌握到这种美女女学妹,让这...
怎么查看老公与别人的微信聊天记录怎样查询我老公的微信聊天记录现阶段在网络推广公司中,有这几种推广是企业一般选择的对象。 No1百度竞价推广(排名不分先后) 优势:(显而易见,毕竟是行业大佬!)...
本文目录一览: 1、求关于黑客的电影和电视剧 2、一部电影,几个黑客控制整个美国,导致交通瘫痪,电力关闭,所有行业受影响,有个警察和黑客恢复 3、求一部电影,一位黑客控制了客机,可以用一个操纵...
带苦味的东西大多数的孩子都会抵抗,那么蜜牙贝贝补铁软胶囊苦不苦呢,孩子会不会抵触呢,蜜牙贝贝补铁软胶囊给孩子吃怎么样,他会不会吐出来呢,友谊长存小编就来说说吧。 宝贝整体 带宝宝去医院体检查了微量...