JavaScript中的延遲和承諾(+ Ember.js示例) 承諾是一種相對較新的處理異步的方式,它可以非常有助於結構化代碼。
警告:此帖子已經過時並且可能不反映最新的技術水平。
請查看我的Promises guide 和我的async/await guide 。
承諾 是事件的對象表示方式。在其生命週期中,當調用時,承諾從待定狀態轉換為已解決或已拒絕狀態,或者它也可以永遠保持待定狀態而不解決。
它是一種對JavaScript事件的新方法,但我認為它生成的代碼更易讀且更少出錯。目前,在JavaScript中有兩種稍有不同的主要承諾實現:遵循Promises/A規範的庫和jQuery。
首先,我將考慮jQuery,因為它無處不在且我使用它,所以如果您不希望使用另外一個外部庫,可以使用它。
介紹jQuery Promise 讓我們介紹延遲的概念。首先,延遲是一個承諾,除了您可以觸發一個延遲(解決或拒絕它)之外,通過承諾,您只能添加回調,並且它將由其他東西觸發。如果您希望,承諾是延遲的“只收聽”部分。
這是一個清晰的例子:
1 var promise = $('div.alert' ).fadeIn ().promise ();
您現在可以添加.done()和.fail()來處理回調。這只是一個調用示例,使用承諾的動畫已經成為jQuery 1.8中的一個真正的東西,同時帶有進度的回調。
另一個例子是AJAX調用:
1 2 var promise = $.get (url);promise.done (function (data ) {});
延遲是您可以創建的東西,設置回調並解決的東西,例如:
1 2 3 var deferred = new $.Deferred ();deferred.done (function (data ) { console .log (data) }); deferred.resolve ('some data' );
延遲的狀態可以使用.resolve()或.reject()觸發。一旦延遲狀態已經更改為最終階段(已解決/已拒絕),它就無法再改變。
1 2 3 4 var deferred = new $.Deferred ();deferred.state (); deferred.resolve (); deferred.state ();
我們可以將以下回調附加到一個承諾:
1 2 3 .done () .fail () .always ()
可以使用.then()
一起調用這些回調,如下所示:
1 promise.then (doneFunc, failFunc, alwaysFunc);
這只是對承諾和延遲的jQuery實現的介紹。讓我們寫一些真實世界的例子。 (如果在node中執行,可以使用$ = require('jquery');
導入jQuery)
一些jQuery示例 例如,這裡我們執行一個函數,當它完成時調用dfd.resolve()。類似於使用回調,但更具結構性和可重用性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $.when (execution ()).then (executionDone); function execution (data ) { var dfd = $.Deferred (); console .log ('start execution' ); setTimeout (function ( ) { dfd.resolve () }, 2000 ); return dfd.promise (); } function executionDone ( ){ console .log ('execution ended' ); }
在這裡,陣列的元素被處理,並且當它們全部都正常(例如一個請求已返回)時,我調用另一個函數。我們開始看到使用Deferred使用的真正好處。使用$.when.apply()
方法在循環中分組了dfd.resolve()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var data = [1 ,2 ,3 ,4 ]; var processItemsDeferred = [];for (var i = 0 ; i < data.length ; i++){ processItemsDeferred.push (processItem (data[i])); } $.when.apply ($, processItemsDeferred).then (everythingDone); function processItem (data ) { var dfd = $.Deferred (); console .log ('called processItem' ); setTimeout (function ( ) { dfd.resolve () }, 2000 ); return dfd.promise (); } function everythingDone ( ){ console .log ('processed all items' ); }
稍微複雜一點的例子,這裡的陣列元素是從外部資源獲取的,使用fetchItemIdsDeferred = fetchItemIds(data)
和fetchItemIdsDeferred.done()
。
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 31 32 33 34 35 36 37 38 39 40 var data = []; var fetchItemIdsDeferred = fetchItemIds (data); function fetchItemIds (data ){ var dfd = $.Deferred (); console .log ('calling fetchItemIds' ); data.push (1 ); data.push (2 ); data.push (3 ); data.push (4 ); setTimeout (function ( ) { dfd.resolve () }, 1000 ); return dfd.promise (); } fetchItemIdsDeferred.done (function ( ) { var processItemsDeferred = []; for (var i = 0 ; i < data.length ; i++){ processItemsDeferred.push (processItem (data[i])); } $.when.apply ($, processItemsDeferred).then (everythingDone); }); function processItem (data ) { var dfd = $.Deferred (); console .log ('called processItem' ); setTimeout (function ( ) { dfd.resolve () }, 2000 ); return dfd.promise (); } function everythingDone ( ){ console .log ('processed all items' ); }
最後兩個示例解釋了如何計算for循環,然後等待處理執行結束後做某些事情。
這是做這件事情最“hacky”的方式:
1 2 3 4 5 6 7 8 var allProcessed = false ;var countProcessed = 0 ;for (var i = 0 , len = theArray.length ; i < len; i++) { (function (i ) { if (++countProcessed === len) allProcessed = true ; })(i); }
現在讓我們看Deferreds可以用於的另一個例子:看看這個
1 2 3 4 5 6 var interval = setInterval (function ( ) { if (App .value ) { clearInterval (interval); } }, 100 );
這是一個評估條件的結構;如果條件為true,代碼將清除間隔並執行if內容。
這對於例如檢查當一個值不再是undefined時很有用:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 var DeferredHelper = { objectVariableIsSet : function (object, variableName ) { var dfd = $.Deferred (); var interval = setInterval (function ( ) { if (object[variableName] !== undefined ) { clearInterval (interval); console .log ('objectVariableIsSet' ); dfd.resolve () } }, 10 ); return dfd.promise (); }, arrayContainsElements : function (array ) { var dfd = $.Deferred (); var interval = setInterval (function ( ) { if (array.length > 0 ) { clearInterval (interval); console .log ('arrayContainsElements' ); dfd.resolve () } }, 10 ); return dfd.promise (); } } var executeThis = function ( ) { console .log ('ok!' ); } var object = {};object.var = undefined ; var array = [];$.when (DeferredHelper .arrayContainsElements (array)).then (executeThis); $.when (DeferredHelper .objectVariableIsSet (object, 'var' )).then (executeThis); setTimeout (function ( ) { object.var = 2 ; array.push (2 ); array.push (3 ); }, 2000 );
上面的示例實際上是三個示例。我創建了一個DeferredHelper對象,它的方法arrayContainsElements和objectVariableIsSet不言自明。
請記住,基本類型按值傳遞,因此您不能這樣做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var integerIsGreaterThanZero = function (integer ) { var dfd = $.Deferred (); var interval = setInterval (function ( ) { if (integer > 0 ) { clearInterval (interval); dfd.resolve () } }, 10 ); return dfd.promise (); }; var variable = 0 ;$.when (integerIsGreaterThanZero (variable)).then (executeThis);
或者你不能這樣做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var object = null ;var variableIsSet = function (object ) { var dfd = $.Deferred (); var interval = setInterval (function ( ) { if (object !== undefined ) { clearInterval (interval); console .log ('variableIsSet' ); dfd.resolve () } }, 10 ); return dfd.promise (); }; $.when (variableIsSet (object)).then (executeThis); setTimeout (function ( ) { object = {}; }, 2000 );
這是因為當對像= {}時,對象參考被更改,而JavaScript實際上通過複製引用來引用變量,variableIsSet函數內部的對象變量的參考與外部對像變量不同。
一個ember.js示例 我在Ember.js中使用的一個功能是:
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 31 32 33 34 35 36 37 38 39 40 App .DeferredHelper = { arrayContainsElements : function (arrayName, object ) { var dfd = $.Deferred (); if (!object) object = App ; var interval = setInterval (function ( ) { if (object.get (arrayName).length > 0 ) { clearInterval (interval); dfd.resolve () } }, 50 ); return dfd.promise (); }, variableIsSet : function (variableName, object ) { var dfd = $.Deferred (); if (!object) object = App ; var interval = setInterval (function ( ) { if (object.get (variableName) !== undefined ) { clearInterval (interval); dfd.resolve () } }, 50 ); return dfd.promise (); } }
因此,我可以在客戶端代碼中這樣做:
1 2 3 4 $.when (App .DeferredHelper .arrayContainsElements ('itemsController.content' )) .then (function ( ) { });
和
1 2 3 4 5 6 7 8 9 10 11 $.when (App .DeferredHelper .variableIsSet ('aVariable' )) .then (function ( ) { }); $.when (App .DeferredHelper .variableIsSet ('aVariable' , anObject)) .then (function ( ) { });
這些示例都是使用jQuery延遲實現的。
如果您不想使用jQuery延遲實現,也許是因為您不使用jQuery並且僅為了延遲而加載它過多,或者您使用的其他庫沒有延遲實現,您可以使用其他專門用於此的庫,例如Q 、rsvp.js 、when.js 。
讓我們使用when.js編寫一些例子。
使用when.js撰寫一些例子 例如,我有一個項目的ID,我想要調用API端點以獲取有關它的更多詳細信息。一旦AJAX調用返回,繼續處理。
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 31 32 33 34 function processItem (item ) { var deferred = when.defer (); var request = $.ajax ({ url : '/api/itemDetails' , type : 'GET' data : { item : item } }); request.done (function (response ) { deferred.resolve (JSON .parse (response)); }); request.fail (function (response ) { deferred.reject ('error' ); }); return deferred.promise ; } var item = { id : 1 } processItem (item).then ( function gotIt (itemDetail ) { console .log (itemDetail); }, function doh (err ) { console .error (err); } );
我從服務器獲取了一些ID值,使用上面的processItem()
函數將它們處理,然後一旦完成處理所有項目,我可以做一些事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function processItems (anArray ) { var deferreds = []; for (var i = 0 , len = anArray.length ; i < len; i++) { deferreds.push (processItem (anArray[i].id )); } return when.all (deferreds); } var anArray = [1 , 2 , 3 , 4 ];processItems (anArray).then ( function gotEm (itemsArray ) { console .log (itemsArray); }, function doh (err ) { console .error (err); } );
when.js庫提供了一些實用的方法,例如when.any()和when.some(),讓延遲的回調在1)解決了其中一個承諾時或者2)至少指定數量的承諾返回時運行。