Deferred 和 Promises 实例详解
最近在网上找了很多关于jquery的Deferred介绍的文章,唯独觉得这篇外文博文介绍的比较详细,现翻译出来:
原文:Deferreds and Promises in JavaScript(http://flaviocopes.com/deferred-and-promises-in-javascript/)
Promises是一种实现javascript异步调用的新方式,它对于结构化你的javascript代码非常有帮助。
一个Promise代表了一个事件对象,在这个对象的整个生命周期中,它从刚开始调用时的挂起状态,到完成调用的解决或拒绝状态,或者它一直处于挂起状态而永远也不被解决。
它是javascript事件的一种新方式,我认为它有助于改善代码的可读性而且你不会觉得很怪异。到目前为止,有两种不同实现Promise的方式,一种是按照Promises/A Spec实现的各种库,另一种就是jQuery。
由于jQuery类库比较常用,因此接下来我将主要用jQuery的实现来讨论它,如果你不想使用其他的库,你也可以使用它。
一、 jQuery promise 介绍:
让我首先介绍下Deferred的概念,首先,Deferred是一个Promise,另外你可以触发一个Deferred(resolve或reject),相反,Promise对象你只能给它添加回调函数,它被其他对象触发。
一个Promise对象,就是一个只读的Deferred对象。
一个简单的例子是下面这个函数:
1 var promise = $("div.alert").fadeIn().Promise();
你现在可以添加done()和fail()来处理回调。
这只是一个调用的例子,在jQuery1.8中,动态的promises变成了实时处理,并且附带了一个progress的回调函数。
另一个promise的例子是ajax调用:
1 var promise = $.get(url);
2 promise.done(function(data) {});
一个deferred对象是你创建的,并且设置了回调函数,并解决的promise,如:
1 var deferred = new $.Deferred();
2 deferred.done(function(data) { console.log(data) });
3 deferred.resolve('some data');
deferred对象的状态可以使用resolve() 和 reject()触发,一旦一个deferred对象的状态变成了最终的状态(resolved/rejected),它就再也不能被改变了如:
1 var deferred = new $.Deferred();
2 deferred.state(); // "pending"
3 deferred.resolve();
4 deferred.state(); // "resolved"
我们可以添加一下这些回调到一个promise对象上:
.done() //当promise执行成功时被调用
.fail() //当promise失败是被调用
.always() //不管是成功和失败状态,最终都会被调用
这些回调函数,可以使用then() , 一起调用:
promise.then(doneFunc , failFunc , alwaysFunc);
上面只是一个简单的介绍jquery对promises和deferred的实现,下面会用几个实例深入讲解:
二、一些jQuery的Deferred实例
比如,下面执行一个函数,当它执行完后调用dfd.resolve(),就像执行了一个回调函数,但代码更加结构化和复用性。
1 $.when(execution()).then(executionDone); 2 3 function execution(data) { 4 var dfd = $.Deferred(); 5 console.log('start execution'); 6 7 //in the real world, this would probably make an AJAX call. 8 setTimeout(function() { dfd.resolve() }, 2000); 9 10 return dfd.promise(); 11 } 12 13 function executionDone(){ 14 console.log('execution ended'); 15 }
实例二:
下面是一个被处理的数组元素,一旦所有的元素都处理完,就调用另外一个函数,让我们来看看使用Deferred的真正好处,$.when.apply()方法用于将所有的dfd.resolve()组织在一起。
1 var data = [1,2,3,4]; // the ids coming back from serviceA 2 var processItemsDeferred = []; 3 4 for(var i = 0; i < data.length; i++){ 5 processItemsDeferred.push(processItem(data[i])); 6 } 7 8 $.when.apply($, processItemsDeferred).then(everythingDone); 9 10 function processItem(data) { 11 var dfd = $.Deferred(); 12 console.log('called processItem'); 13 14 //in the real world, this would probably make an AJAX call. 15 setTimeout(function() { dfd.resolve() }, 2000); 16 17 return dfd.promise(); 18 } 19 20 function everythingDone(){ 21 console.log('processed all items'); 22 }
实例三:
一个稍微更复杂点的例子,在这里数组里的元素从其他地方获取,使用var fetchItemIdsDeferred = fetchItemIds(data) and fetchItemIdsDeferred.done()
1 var data = []; // the ids coming back from serviceA 2 var fetchItemIdsDeferred = fetchItemIds(data); // has to add the ids to data 3 4 function fetchItemIds(data){ 5 var dfd = $.Deferred(); 6 console.log('calling fetchItemIds'); 7 8 data.push(1); 9 data.push(2); 10 data.push(3); 11 data.push(4); 12 13 setTimeout(function() { dfd.resolve() }, 1000); 14 return dfd.promise(); 15 } 16 17 fetchItemIdsDeferred.done(function() { // if fetchItemIds successful... 18 var processItemsDeferred = []; 19 20 for(var i = 0; i < data.length; i++){ 21 processItemsDeferred.push(processItem(data[i])); 22 } 23 24 $.when.apply($, processItemsDeferred).then(everythingDone); 25 }); 26 27 28 function processItem(data) { 29 var dfd = $.Deferred(); 30 console.log('called processItem'); 31 32 //in the real world, this would probably make an AJAX call. 33 setTimeout(function() { dfd.resolve() }, 2000); 34 35 return dfd.promise(); 36 } 37 38 function everythingDone(){ 39 console.log('processed all items'); 40 }
实例四:
下面是两个我实例,在不用Defrred的情况下,这个程序周期性做同一件事,并等待处理结束后执行其他事情。
常用的做法如下:
1 var allProcessed = false;
2 var countProcessed = 0;
3 for (var i = 0, len = theArray.length; i < len; i++) {
4 (function(i) {
5 // do things with i
6 if (++countProcessed === len) allProcessed = true;
7 })(i);
8 }
另外一个例子:
1 var interval = setInterval(function() { 2 if (App.value) { 3 clearInterval(interval); 4 // do things 5 } 6 }, 100);
这是一种结构,代码周期性的判断一个条件是否为true,如果是,则clear interval,并且执行里面的代码。
下面是使用Deferred处理上述情况:
1 var DeferredHelper = { 2 objectVariableIsSet: function(object, variableName) { 3 var dfd = $.Deferred(); 4 5 var interval = setInterval(function() { 6 if (object[variableName] !== undefined) { 7 clearInterval(interval); 8 console.log('objectVariableIsSet'); 9 dfd.resolve() 10 } 11 }, 10); 12 13 return dfd.promise(); 14 }, 15 16 arrayContainsElements: function(array) { 17 var dfd = $.Deferred(); 18 19 var interval = setInterval(function() { 20 if (array.length > 0) { 21 clearInterval(interval); 22 console.log('arrayContainsElements'); 23 dfd.resolve() 24 } 25 }, 10); 26 27 return dfd.promise(); 28 } 29 } 30 31 var executeThis = function() { 32 console.log('ok!'); 33 } 34 35 var object = {}; 36 object.var = undefined; 37 var array = []; 38 39 $.when(DeferredHelper.arrayContainsElements(array)).then(executeThis); 40 $.when(DeferredHelper.objectVariableIsSet(object, 'var')).then(executeThis); 41 42 setTimeout(function() { 43 object.var = 2; 44 array.push(2); 45 array.push(3); 46 }, 2000);
上面的例子,实际上集中了以上三个的功能,我首先创建了一个DeferredHelper对象并且他的另外两个方法arrayContainsElements和objectVariableIsSet功能是自解释的。
考虑到初始值传进来不能做
1 var integerIsGreaterThanZero = function(integer) { 2 var dfd = $.Deferred(); 3 4 var interval = setInterval(function() { 5 if (integer > 0) { 6 clearInterval(interval); 7 dfd.resolve() 8 } 9 }, 10); 10 11 return dfd.promise(); 12 }; 13 14 var variable = 0; 15 16 $.when(integerIsGreaterThanZero(variable)).then(executeThis);
也不能这样做:
1 var object = null; 2 3 var variableIsSet = function(object) { 4 var dfd = $.Deferred(); 5 6 var interval = setInterval(function() { 7 if (object !== undefined) { 8 clearInterval(interval); 9 console.log('variableIsSet'); 10 dfd.resolve() 11 } 12 }, 10); 13 14 return dfd.promise(); 15 }; 16 17 $.when(variableIsSet(object)).then(executeThis); 18 19 setTimeout(function() { 20 object = {}; 21 }, 2000);
because when doing object = {}, the object reference is changed, and as Javascript actually references variables by copy-reference, the reference of the object variable inside the variableIsSet function is not the same as the outer object variable.
一个在ember.js中的例子:
1 App.DeferredHelper = { 2 3 /** 4 * Check if an array has elements on the App global object if object 5 * is not set. 6 * If object is set, check on that object. 7 */ 8 arrayContainsElements: function(arrayName, object) { 9 var dfd = $.Deferred(); 10 if (!object) object = App; 11 12 var interval = setInterval(function() { 13 if (object.get(arrayName).length > 0) { 14 clearInterval(interval); 15 dfd.resolve() 16 } 17 }, 50); 18 19 return dfd.promise(); 20 }, 21 22 /** 23 * Check if a variable is set on the App global object if object 24 * is not set. 25 * If object is set, check on that object. 26 */ 27 variableIsSet: function(variableName, object) { 28 var dfd = $.Deferred(); 29 if (!object) object = App; 30 31 var interval = setInterval(function() { 32 if (object.get(variableName) !== undefined) { 33 clearInterval(interval); 34 dfd.resolve() 35 } 36 }, 50); 37 38 return dfd.promise(); 39 } 40 }
因此我在客户端可以使用:
1 $.when(App.DeferredHelper.arrayContainsElements('itemsController.content')) 2 .then(function() { 3 //do things 4 });
和
1 $.when(App.DeferredHelper.variableIsSet('aVariable')) 2 .then(function() { 3 //do things 4 }); 5 6 //& 7 8 $.when(App.DeferredHelper.variableIsSet('aVariable', anObject)) 9 .then(function() { 10 //do things 11 });
所有这些都使用了jQuery的deferreds 实现。
其他的资源:
http://robdodson.me/blog/2012/06/03/make-your-own-jquery-deferreds-and-promises/
http://msdn.microsoft.com/en-us/magazine/gg723713.aspx
也许你并不想使用jQuery的deferred实现,或者你使用其他的库但没有实现这个功能,你可以使用其他专有的库如Q, rsvp.js, when.js.
让我们看看是哟个when.js的例子:
例如:我有一个元素的ID,我想在终点调用API获取到更多详细信息,一旦ajax调用返回,继续处理:
1 function processItem(item) {
2 var deferred = when.defer();
3
4 var request = $.ajax({
5 url: '/api/itemDetails',
6 type: 'GET'
7 data: {
8 item: item
9 }
10 });
11
12 request.done(function(response) {
13 deferred.resolve(JSON.parse(response));
14 });
15
16 request.fail(function(response) {
17 deferred.reject('error');
18 });
19
20 return deferred.promise;
21 }
22
23 var item = {
24 id: 1
25 }
26
27 processItem(item).then(
28 function gotIt(itemDetail) {
29 console.log(itemDetail);
30 },
31 function doh(err) {
32 console.error(err);
33 }
34 );
我从服务器获取了一些ID值,使用函数processItem()处理它们,然后一旦处理了所有这些元素,我就做其他事:
1 function processItems(anArray) {
2 var deferreds = [];
3
4 for (var i = 0, len = anArray.length; i < len; i++) {
5 deferreds.push(processItem(anArray[i].id));
6 }
7
8 return when.all(deferreds);
9 }
10
11 var anArray = [1, 2, 3, 4];
12
13 processItems(anArray).then(
14 function gotEm(itemsArray) {
15 console.log(itemsArray);
16 },
17 function doh(err) {
18 console.error(err);
19 }
20 );
when.js库提供了一些工具方法比如when.any()和when.some(),这些让deferred的回调函数运行当1.有一个promise被solved,2.至少有一个特定的promise已经返回了。

浙公网安备 33010602011771号