异步JS:$.Deferred的使用css
原文连接:http://www.html5rocks.com/en/tutorials/async/deferred/html
当咱们构建一个平稳的,响应式的HTML5应用时,其中一个很是重要的方面是在不一样部分的应用中的同步,例如数据获取,程序处理, 动画和用户界面元素。html5
在桌面和原生环境之间,一个主要的区别就是浏览器不给访问线程模型,但会为用户界面(例如DOM)提供一个单线程的访问。这意味着全部的应用程序逻辑访问和修改用户界面元素老是在同一线程中,所以要保证程序的工做单位尽量的短小和高效,以及尽可能多的使用更有优点的浏览器提供的异步能力。git
浏览器异步APIsweb
很幸运,浏览器提供了一些异步API,例如很经常使用的XHR(XMLHttpRequest或AJAX)API,还有IndexedDB, SQLite, HTML5 Web workers和HTML5 GeoLocation (地理定位)API,甚至一些DOM相关的行为都有异步,例如经过transitionEnd的事件的CSS3动画。ajax
浏览器暴露异步编程给程序逻辑的方式是经过事件或者回调。在基于事件的异步API中,开发者给一个已存在的对象(例如HTML元素或者其余DOM对象)注册一个事件处理程序,而后执行它。浏览器一般会在不一样的线程上表现行为,适当的时候会在主线程中触发事件。编程
举个例子,写段有段XHR API,一个基于事件的异步API,将会像这样:promise
// Create the XHR object to do GET to /data resource 浏览器
var xhr = new XMLHttpRequest();缓存
xhr.open("GET","data",true);
// register the event handler
xhr.addEventListener('load',function(){
if(xhr.status === 200){
alert("We got data: " + xhr.response);
}
},false)
// perform the work
xhr.send();
另外一个基于事件的异步API CSS3 transitionEnd事件:
// get the html element with id 'flyingCar'
var flyingCarElem = document.getElementById("flyingCar");
// register an event handler
// ('transitionEnd' for FireFox, 'webkitTransitionEnd' for webkit)
flyingCarElem.addEventListener("transitionEnd",function(){
// will be called when the transition has finished.
alert("The car arrived");
});
// add the CSS3 class that will trigger the animation
// Note: some browers delegate some transitions to the GPU , but
// developer does not and should not have to care about it.
flyingCarElemen.classList.add('makeItFly')
其余的浏览器API,例如SQLite和HTML5地理定位都是基于回调的。
这是一段HTML5地理定位的例子:
// call and pass the function to callback when done.
navigator.geolocation.getCurrentPosition(function(position){
alert('Lat: ' + position.coords.latitude + ' ' +
'Lon: ' + position.coords.longitude);
});
在这个例子中,咱们执行了一个方法和将一个函数做为参数传入,而后将会获得请求的结果。这使得浏览器可以同步或异步方式实现这个功能,给一个单一的API给开发者,而无论实施细节
开始使用异步
除了浏览器内置的异步API,良好构建的应用程序也应该将它们的低级(基础)的API以异步的方式暴露出来,特别是当它们要进行I/O操做或者庞大的计算处理。再举个例子,获取数据的API应该以异步的方式实现,而不该该像这样:
// WRONG: this will make the UI freeze when getting the data
var data = getData();
alert("We got data: " + data);
这个API的设计须要getData()为阻塞,但这会冻结用户界面直到获取到了数据。若是数据在JS上下文的内部,这个影响微乎其微,可是若是是经过网络获取或者在SQLite或者index存储中,这将会对用户体验产生戏剧性的影响。
正确的API设计是主动地让全部程序API可以执行一段时间,一开始就异步,由于将同步的代码改造为异步的代码会是一个艰巨的任务。
例如,简单化的getData() API应该像这样:
getData(function(data){
alert("We got data: " + data);
});
这样的好处是这会让应用程序的UI代码一开始以异步为中心,而后容许相关的API决定将来是否须要异步或同步。
注意,并非全部的应用程序API都须要或者应该异步。断定准则是任何API操做I/O或者处理大型数据(超过15毫秒)都应该一开始就异步,即便第一个的实现是同步的。
失败处理
传统的方式是使用try/catch来处理异步编程中的错误,可是这种方式并非真正的奏效,由于错误一般发生在另外一个线程中。因此,当前执行的函数须要一个结构化的方式来通知上一级的函数当在处理过程当中发生了错误。
在基于事件的异步API中,当接受事件时,一般是根据应用程序代码查询事件或者对象来完成的。而基于回调的异步API中,最好的实践方式是给第二个参数传入错误时须要执行的函数。
咱们的getData应该像这样:
// getData(successFunc,failFunc);
getData(function(data){
alert("We got data: " + data);
}, function(ex){
alert("oops, some problem occured: " + ex);
});
和$.Deferred结合使用
上述回调的一个限制是当咱们写同步逻辑的时候,代码将会变得很繁琐。
例如,若是你须要等待两个异步的API完成采起作第三件事时,代码复杂度将会马上提高:
// first do the get data.
getData(function(data){
// then get the location
getLocation(function(location){
alert("we got data: " + data + " and location: " + location);
},function(ex){
alert("getLocation failed: " + ex);
});
},function(ex){
alert("getData failed: " + ex);
});
当多个程序须要调用相同的回调时,事件会变得更加复杂。由于每次调用将必须执行这些多步骤调用,不然应用程序将不得不实施其本身的缓存机制。
很幸运,有一种相关的旧的模式叫作Promises(相似于Java的Future),jQuery核心提供了一个健壮的现代实现,$.Deferred针对异步编程提供了一个简单而又强大的解决方案。
为了简便,Promises模式定义了异步API返回一个Promise对象,上一级函数得到Promise对象,而后执行done(successFunc(data)),告诉Promise对象当data解决(获取)了,就执行successFunc这个函数。
例子:
// get the promise object for this API
var dataPromise = getData();
// register a function to get called when the data is resolved
dataPromise.done(function(data){
alert("We got data: " + data);
});
// register the failure function
dataPromise.fail(function(ex){
alert("oops, some problem occured: " + ex);
});
// Note: we can have as many dataPromise.done(...) as we want.
dataPromise.done(function(data){
alert("We asked it twice, we get it twice: " + data);
});
这里咱们首先得到了dataPromise对象,而后调用.done方法来注册一个当数据获得解决咱们想要执行的函数。咱们也能够调用.fail方法来处理错误结果。咱们还能够执行更多咱们须要的.done或者.fail方法,由于jQuery相关的Promise实现会处理注册和回调。
有了这种模式,如今想要实现更高级的异步代码就更简单了,并且jQuery已经提供了很是经常使用的$.when这个方法。
举个例子,上面被嵌套的getData/getLocation 回调应该像这样:
// assuming both getData and getLocation return their respective Promise
var combinedPromise = $.when(getData(), getLocation())
// function will be called when both getData and getLocation resolve
combinePromise.done(function(data,location){
alert("We got data: " + data + " and location: " + location);
});
使用jQuery.Deferred的美妙之处在于开发者能够很轻松的实现异步函数,例如像这样的getData:
function getData(){
// 1) create the jQuery Deferred object that will be used
var deferred = $.Deferred();
// ---- AJAX Call ---- //
var xhr = new XMLHttpRequest();
xhr.open("GET","data",true);
// register the event handler
xhr.addEventListener('load',function(){
if(xhr.status === 200){
// 3.1) RESOLVE the DEFERRED (this will trigger all the done()...)
deferred.resolve(xhr.response);
}else{
// 3.2) REJECT the DEFERRED (this will trigger all the fail()...)
deferred.reject("HTTP error: " + xhr.status);
}
},false)
// perform the work
xhr.send();
// Note: could and should have used jQuery.ajax.
// Note: jQuery.ajax return Promise, but it is always a good idea to wrap it
// with application semantic in another Deferred/Promise
// ---- /AJAX Call ---- //
// 2) return the promise of this deferred
return deferred.promise();
}
当getData()被调用时,它一开始会建立一个新的jQuery.Deferred对象(1),而后返回它的Promise对象(2),这样getData就能够注册它的done和fail方法。接着,当XHR执行返回,它既可能解决这个deferred(3.1)也可能拒绝它(3.2)。当解决deferred时将会触发全部done函数和其余的promise函数(例如then和pipe),拒绝deferred则会执行fail函数。
使用场景:
数据访问: 在远程数据访问时,异步远程调用将会很明显的影响用户体验。并且在本地数据中像低级的API(SQLite和IndexedDB)自己就是异步的。Deferred API的 $.when和 .pipe在同步和链式异步子查询时很是强大。
UI动画: 编写一个或多个动画的transitionEnd事件是一件很是乏味的事,特别是当动画师CSS3动画和JS的混合。将animarion函数用Deferred包装起来能够显著地减小代码复杂度和提高灵活性。甚至一个简单通用的包装器函数像cssAnimation(className)返回一个Promise对象(当transitionEnd时获得解决)都将受益不浅。
UI组件显示: 这个有点高级,可是高级的HTML组件框架应该也使用Deferred。当一个应用程序须要显示不一样部分的用户界面,将组件经过Deferred封装能够更大的控制生命周期。
任何的异步API: 将浏览器API经过Deferred包装,在字面上一个只用加4-5行代码,但却会极大地简化应用程序代码。
缓存化: 这是一种附带的好处,但在一些场合里这会很是有用。在异步调用时可将Deferred 对象缓存起来。好处是调用者不须要知道调用是否被解决或者正在被解决中,它的回调函数将会彻底以相同的方式被调用。
结论
$.Deferred概念很简单,可是须要一段时间才能掌握好它。掌握js异步编程对任何HTML5应用程序开发者而言是很必要的,并且Promise模式使异步编程更可靠和强大!