2014-09-06 43 views
12

在承諾庫Q,你可以做以下順序鏈的承諾:

var items = ['one', 'two', 'three']; 
var chain = Q(); 
items.forEach(function (el) { 
    chain = chain.then(foo(el)); 
}); 
return chain; 

但是,以下不$ Q工作:

var items = ['one', 'two', 'three']; 
var chain = $q(); 
items.forEach(function (el) { 
    chain = chain.then(foo(el)); 
}); 
return chain; 

回答

29

只需使用$ q.when()函數:

var items = ['one', 'two', 'three']; 
var chain = $q.when(); 
items.forEach(function (el) { 
    chain = chain.then(foo(el)); 
}); 
return chain; 

注意:foo m最好是工廠,例如

function setTimeoutPromise(ms) { 
    var defer = $q.defer(); 
    setTimeout(defer.resolve, ms); 
    return defer.promise; 
} 

function foo(item, ms) { 
    return function() { 
    return setTimeoutPromise(ms).then(function() { 
     console.log(item); 
    }); 
    }; 
} 

var items = ['one', 'two', 'three']; 
var chain = $q.when(); 
items.forEach(function (el, i) { 
    chain = chain.then(foo(el, (items.length - i)*1000)); 
}); 
return chain; 
+3

這是行不通的。它同時執行它們全部。我知道這一點,因爲我執行一系列需要大約500個MS的請求。看着我的網絡流量,他們都同時出去(但是按順序)。 – FlavorScape 2015-03-19 23:45:03

+0

@FlavorScape,確保foo是一個工廠 - 查看我的編輯。 – redgeoff 2015-03-20 00:21:00

+1

啊,確定它是一個工廠,這樣它就不會立即在調用堆棧中執行,當我們構建鏈時,對吧? – FlavorScape 2015-03-20 00:50:44

28

Redgeoff,你自己的答案是我用於轉化爲數組鏈一系列承諾的方式。

緊急事實上模式如下:

function doAsyncSeries(arr) { 
    return arr.reduce(function (promise, item) { 
     return promise.then(function(result) { 
     return doSomethingAsync(result, item); 
     }); 
    }, $q.when(initialValue)); 
} 

//then 
var items = ['x', 'y', 'z']; 
doAsyncSeries(items).then(...); 

注:

  • .reduce是原始的JavaScript,而不是一個庫的一部分。
  • result是以前的異步結果/數據,爲了完整而包括在內。初始resultinitialValue。如果沒有必要傳遞結果,那就簡單地將它排除在外。
  • adapt $q.when(initialValue)取決於您使用哪個promise lib。
  • 在你的情況下,doSomethingAsyncfoo(或什麼foo()返回?) - 在任何情況下,一個函數。

如果你和我一樣,那麼這個圖案看起來就像一個無法穿透的衣服,但是一旦你的眼睛變得適合,你就會開始認爲它是一個老朋友。

編輯

這裏有一個demo,旨在證明,上述推薦的模式其實不按順序執行其doSomethingAsync()電話,沒有立即在構建鏈在下面的意見建議。

+1

原始答案在語法上不正確。我修正了這一點。另外,initialValue應該設置爲什麼?就像上面的答案一樣,這將會同時激發所有人。 – FlavorScape 2015-03-19 23:54:09

+0

@FlavorScape,很好的捕捉。很高興知道有人在那裏檢查這些東西。 – 2015-03-20 08:57:07

+0

'initialValue'在'reduce()'循環的第一次迭代中顯示爲'result'。它的價值取決於應用程序。如果'doSomethingAsync()'不需要傳遞給它的前一個結果,那麼簡化初始化器將簡化爲'$ q.when()' – 2015-03-20 09:06:17

2

我更喜歡使用angular.bind(或Function.prototype.bind)返回promise的函數,然後使用reduce快捷方式將它們鏈接到一個鏈中。例如

// getNumber resolves with given number 
var get2 = getNumber.bind(null, 2); 
var get3 = getNumber.bind(null, 3); 
[get2, get3].reduce(function (chain, fn) { 
    return chain.then(fn); 
}, $q.when()) 
.then(function (value) { 
    console.log('chain value =', value); 
}).done(); 
// prints 3 (the last value) 
+0

您可否在這種情況下詳細說明'angular.bind'的好處? – pulkitsinghal 2015-06-17 16:01:44

+0

我使用綁定來創建不需要任何參數的promise返回函數。因此,它們可以像'.then(foo).then(bar)'那樣使用。' – 2015-06-18 16:18:21

2

你的回答是正確的。不過,我想我會提供一個替代方案。如果您發現自己經常連續承諾承諾,您可能對$q.serial感興趣。

var items = ['one', 'two', 'three']; 
var tasks = items.map(function (el) { 
    return function() { foo(el, (items.length - i)*1000)); }); 
}); 

$q.serial(tasks); 

function setTimeoutPromise(ms) { 
    var defer = $q.defer(); 
    setTimeout(defer.resolve, ms); 
    return defer.promise; 
} 

function foo(item, ms) { 
    return function() { 
    return setTimeoutPromise(ms).then(function() { 
     console.log(item); 
    }); 
    }; 
} 
+1

這看起來很有趣,我將在今天爲我的平板寫入百萬個單元測試後嘗試重構... – FlavorScape 2015-04-06 18:47:11

+0

這絕對是一個更簡單因爲稍後閱讀代碼更容易。 – supersan 2017-02-18 16:06:56

2

在也許比redgeoff's answer方式簡單,如果你不需要它的自動化,如在this post開始你可以鏈的承諾使用$q.when().then()結合。 return $q.when() .then(function(){ return promise1; }) .then(function(){ return promise2; });

3

更改此:

var items = ['one', 'two', 'three']; 

這樣:

var items = [ 
    foo.bind(null, 'one'), 
    foo.bind(null, 'two'), 
    foo.bind(null, 'three') 
]; 

然後一行:

return items.reduce($q.when, $q.resolve()); 
1
var when = $q.when(); 

for(var i = 0; i < 10; i++){ 
    (function() { 
     chain = when.then(function() { 
     return $http.get('/data'); 
     }); 

    })(i); 
}