这篇文章主要介绍了深入理解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吧 !
本文目录一览: 1、河南黑客进入国家机关后台网干了什么惊天大事? 2、永恒之蓝勒索蠕虫病毒攻破哪些网 3、公安局网络安全大队是干什么的 4、任何个人和组织有权对危害网络安全的行为向什么部门...
本文导读目录: 1、求很好笑很好笑的极品笑话!一定要超级好笑的那种! 2、短篇言情小说 3、【高分求小说】现代言情短篇小说(内详) 4、黑客大神:黑客可以厉害到什么程度 5、求短篇言情...
本文目录一览: 1、防止黑客攻击的方法有哪些? 2、如何防止网站被黑客攻击? 3、数据库安全的防护手段 防止黑客攻击的方法有哪些? 从技术上对付黑客攻击,主要采用下列方法:(1)使用防火墙技...
一加科技可能在本月末宣布发布一款称为“轻旗舰级”级智能机――一加手机X。有关这款手机上的配备和外型,杰出的国家工信部早已在第一时间为大家产生了有关信息,但是其市场价却依然是个谜。 大家都知道,除开国...
商标注册其实不是很复杂,但是外行人第一次接触难免都会懵掉!咱今天就捡着重要的来说吧! 商标注册,首先要做好一个长时间战斗的准备! 一个商标从申请到核准注册,大约需一年至一年半时间。商标注...
同步微信不被发现 导出的微信纪录能够在电脑上稽查吗? 删除微信谈天纪录后,许多人有望规复这些纪录。 小编也是一个赊账的人。 他时常在清空微信密友或清空微信缓存时误删除微信数据。 朋友们都晓得...