异步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的使用的更多相关文章

  1. 异步队列 Deferred

    异步队列 Deferred 背景: 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe ...

  2. 移动web app开发必备 - 异步队列 Deferred

    背景 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe(原理类似) setTimeo ...

  3. 嵌套回调异步与$.Deferred异步

    HTML: <input type="button" id="btn1" value="嵌套回调异步"> <input t ...

  4. jquery管理ajax异步-deferred对象

    今天跟大家分享一个jquery中的对象-deferred.其实早在jquery1.5.0版本中就已经引入这个对象了.不过可能在实际开发过程中用到的并不多,所以没有太在意. 这里先不说deferred的 ...

  5. jQuery异步框架探究2:jQuery.Deferred方法

    (本文针对jQuery1.6.1版本号)关于Deferred函数的描写叙述中有一个词是fledged,意为"羽翼丰满的",说明jQuery.Deferred函数应用应该更成熟. 这 ...

  6. 移动web app开发必备 - Deferred 源码分析

    姊妹篇  移动web app开发必备 - 异步队列 Deferred 在分析Deferred之前我觉得还是有必要把老套的设计模式给搬出来,便于理解源码! 观察者模式 观察者模式( 又叫发布者-订阅者模 ...

  7. jQuery源码分析系列(31) : Ajax deferred实现

    AJAX的底层实现都是浏览器提供的,所以任何基于api上面的框架或者库,都只是说对于功能的灵活与兼容维护性做出最优的扩展 ajax请求的流程: 1.通过 new XMLHttpRequest 或其它的 ...

  8. 读jQuery源码 - Deferred

    Deferred首次出现在jQuery 1.5中,在jQuery 1.8之后被改写,它的出现抹平了javascript中的大量回调产生的金字塔,提供了异步编程的能力,它主要服役于jQuery.ajax ...

  9. AngularJS 的异步服务测试与Mocking

    测试 AngularJS 的异步服务 最近,在做项目时掉进了 AngularJS 异步调用 $q 测试的坑中,直接躺枪了.折腾了许久日子,终于想通了其中的道道,但并不确定是最佳的解决方案,最后还是决定 ...

  10. Deferred对象

    摘要:虽然js已经出了ES6,ES7等等版本,从而也诞生了新的异步对象->promise,但是基础还是要终结的,这一片就来回顾一下ajax以及ajax的异步对象->deferred. 1. ...

随机推荐

  1. Ubuntu kylin系统改中文系统文件名为英文

    刚装好系统,将使用语言改成了中文,结果重启后,提示是否将文件系统的名字改为新的,我一不注意,点了是...这样,在以后使用终端的时候,会有中文来干扰,所以需要改回英文. 方法如下: 输入两个命令即可: ...

  2. matlab封装DLL混合编程总结

    最近做了个项目要用到matlab做些算法处理,然后用.net项目调用这个类,我把这个matlab封装dll总结了下如下: matlab是商业数学软件,优势是在算法开发上面有很强的功能,提供了很多数学算 ...

  3. throw和throws

    uncheckException的处理 class User{ private int age; public void setAge(int age){ if(age < 0){ //生成异常 ...

  4. Linux软链接与硬链接

    1.Linux链接概念Linux链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link).默认情况下,ln命令产生硬链接. [硬连接]硬连接指通过索引节点 ...

  5. hdu 1269 迷宫城堡

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=1269 迷宫城堡 Description 为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个 ...

  6. Swift学习初步(一)

    前几天刚刚将有关oc的教程草草的看了一遍,发现oc其实也不像传说的那么难.今天又开始马不停蹄的学习Swift因为我很好奇,到底苹果出的而且想要代替oc的编程语言应该是个什么样子呢?看了网上的一些中文教 ...

  7. Linux下强制修改root密码方法(图)

    如果Linux操作系统的root密码,那怎么办呢?方法很多,下面再给大家介绍一种. [1] 进入以下画面后,按下e按钮,进入编辑模式: [2]进入以下的画面后,选择如下所示的选项,再次按下e按钮: 然 ...

  8. c 计算 语句 执行 时间

    当然,你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间:   #include “stdio.h” #include “stdlib.h” #include “tim ...

  9. 小组开发项目针对性的NABC分析

    单独就我们团队开发项目——重力解锁的功能特点而言,我们解决了智能手机屏幕解锁的乏味和繁琐的特点,显得更有趣味性和独特性,更符合现代人追随时尚的潮流:我们根据个人的不同喜好和便利性来设定一些动作,利用重 ...

  10. Spring事务总结

    事务是什么? 事务就是用来解决类似问题,事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败,那么事务就会回滚到最开始的状态,仿佛什么都没有发生过.在企业级应 ...