异步JS:$.Deferred的使用
异步JS:$.Deferred的使用
原文链接:http://www.html5rocks.com/en/tutorials/async/deferred/
当我们构建一个平稳的,响应式的HTML5应用时,其中一个非常重要的方面是在不同部分的应用中的同步,例如数据获取,程序处理, 动画和用户界面元素。
在桌面和原生环境之间,一个主要的区别就是浏览器不给访问线程模型,但会为用户界面(例如DOM)提供一个单线程的访问。这意味着所有的应用程序逻辑访问和修改用户界面元素总是在同一线程中,因此要保证程序的工作单位尽可能的短小和高效,以及尽量多的使用更有优势的浏览器提供的异步能力。
浏览器异步APIs
很幸运,浏览器提供了一些异步API,例如很常用的XHR(XMLHttpRequest或AJAX)API,还有IndexedDB, SQLite, HTML5 Web workers和HTML5 GeoLocation (地理定位)API,甚至一些DOM相关的行为都有异步,例如通过transitionEnd的事件的CSS3动画。
浏览器暴露异步编程给程序逻辑的方式是通过事件或者回调。在基于事件的异步API中,开发者给一个已存在的对象(例如HTML元素或者其他DOM对象)注册一个事件处理程序,然后执行它。浏览器通常会在不同的线程上表现行为,适当的时候会在主线程中触发事件。
举个例子,写段有段XHR API,一个基于事件的异步API,将会像这样:
// 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模式使异步编程更可靠和强大!
异步JS:$.Deferred的使用的更多相关文章
- 异步队列 Deferred
异步队列 Deferred 背景: 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe ...
- 移动web app开发必备 - 异步队列 Deferred
背景 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe(原理类似) setTimeo ...
- 嵌套回调异步与$.Deferred异步
HTML: <input type="button" id="btn1" value="嵌套回调异步"> <input t ...
- jquery管理ajax异步-deferred对象
今天跟大家分享一个jquery中的对象-deferred.其实早在jquery1.5.0版本中就已经引入这个对象了.不过可能在实际开发过程中用到的并不多,所以没有太在意. 这里先不说deferred的 ...
- jQuery异步框架探究2:jQuery.Deferred方法
(本文针对jQuery1.6.1版本号)关于Deferred函数的描写叙述中有一个词是fledged,意为"羽翼丰满的",说明jQuery.Deferred函数应用应该更成熟. 这 ...
- 移动web app开发必备 - Deferred 源码分析
姊妹篇 移动web app开发必备 - 异步队列 Deferred 在分析Deferred之前我觉得还是有必要把老套的设计模式给搬出来,便于理解源码! 观察者模式 观察者模式( 又叫发布者-订阅者模 ...
- jQuery源码分析系列(31) : Ajax deferred实现
AJAX的底层实现都是浏览器提供的,所以任何基于api上面的框架或者库,都只是说对于功能的灵活与兼容维护性做出最优的扩展 ajax请求的流程: 1.通过 new XMLHttpRequest 或其它的 ...
- 读jQuery源码 - Deferred
Deferred首次出现在jQuery 1.5中,在jQuery 1.8之后被改写,它的出现抹平了javascript中的大量回调产生的金字塔,提供了异步编程的能力,它主要服役于jQuery.ajax ...
- AngularJS 的异步服务测试与Mocking
测试 AngularJS 的异步服务 最近,在做项目时掉进了 AngularJS 异步调用 $q 测试的坑中,直接躺枪了.折腾了许久日子,终于想通了其中的道道,但并不确定是最佳的解决方案,最后还是决定 ...
- Deferred对象
摘要:虽然js已经出了ES6,ES7等等版本,从而也诞生了新的异步对象->promise,但是基础还是要终结的,这一片就来回顾一下ajax以及ajax的异步对象->deferred. 1. ...
随机推荐
- Partitioner没有被调用的情况
map的输出,通过分区函数决定要发往哪个reducer. 有2种情况,我们自定义的Partitioner不会被调用 1) reducer个数为0 这种情况,没有reducer,不需要分区 2) red ...
- python爬取糗百第一页的笑话
自学python网络爬虫,发现request比urllib还是要好用一些,因此利用request和BeautifulSoup来实现糗百的首页笑话的抓取.BeautifulSoup通过find和find ...
- IE8中JSON.stringify方法对自动转换unicode字符的解决方案
IE8内置了JSON对象,用以处理JSON数据.与标准方法的不同,IE8的JSON.stringify会把utf-8字符转码: var str = "我是程序员" var json ...
- 微软TTS尝试系列之开篇杂谈(仅思路)
第一次写博客,不知道如何下手,思路也乱,就先聊聊怎么进的园子吧,但愿不会浪费大家太多的宝贵时间>_<. 与博客园结缘应该是大三刚开始的时候.当时学校教务处想开发一个教务安排系统,为了省钱就 ...
- 1008. Elevator (20)
The highest building in our city has only one elevator. A request list is made up with N positive nu ...
- 为了android sdk下载,必须修改hosts
#Download 下载 203.208.46.146 dl.google.com 203.208.46.146 dl-ssl.google.com #Groups 203.208.46.146 gr ...
- poj 1459 Power Network
题目连接 http://poj.org/problem?id=1459 Power Network Description A power network consists of nodes (pow ...
- hdu 5058 So easy
题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=5058 So easy Description Small W gets two files. Ther ...
- "奇葩家园“之genymotion工具篇
genymotion 简直就是android开发者的福音,比android内置的模拟器不知道快多少, 具体的安装可以参考如下: 1.登陆官方网站,必须先注册 https://www.genymotio ...
- jquery-弹窗:layer
键: 值 描述 下表的属性都是默认值,您可在调用时按需重新配置,他们可帮助你实现各式各样的风格.如是调用: $.layer({键: 值, 键: 值, …}); type: 0 层的类型.0:信息框(默 ...