可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am using the Q javascript promises library and am running in a browser, and I want to figure out how to chain together groups of promises so that each group gets executed sequentially. For example, if I have items A, B, C, and D, I want to group A and B together and then C and D together, so that both A and B must fulfill before C and D get executed. I created this simple jsfiddle to show my current attempt.
var work_items = [ 'A','B','C','D','E','F','G','H','I' ];
var n = 2; // group size
var wait = 1000;
var getWorkPromiseFn = function (item) {
log("Getting promise function for " + item);
return function () {
log("Starting " + item);
var deferred = Q.defer();
setTimeout(function () {
var status = "Finished " + item;
log(status);
deferred.resolve(status);
}, wait);
return deferred.promise;
};
};
var queue = Q();
//log('Getting sequentially'); // One-by-one in sequence works fine
//work_items.forEach(function (item) {
// queue = queue.then(getWorkPromiseFn(item));
//});
log('Getting ' + n + ' at a time'); // This section does not
while (work_items.length > 0) {
var unit = [];
for (var i=0; i<n; i++) {
var item = work_items.shift();
if (item) {
unit.push(getWorkPromiseFn(item));
}
}
queue.then(Q.all(unit));
}
var inspect = queue.inspect(); // already fulfilled, though no work is done
It looks like I am probably passing the wrong array to Q.all
here, since I'm passing in an array of functions which return promises rather than an array of the promises themselves. When I tried to use promises directly there (with unit.push(Q().then(getWorkPromiseFn(item));
for example), the work for each was begun immediately and there was no sequential processing. I guess I'm basically unclear on a good way to represent the group in a way that appropriately defers execution of the group.
So, how can I defer execution of a group of promises like this?
回答1:
This can be done by first pre-processing the array of items into groups, then applying the two patterns (not the anti-patterns) provided here under the heading "The Collection Kerfuffle".
The main routine can be coded as a single chain of array methods.
var work_items = [ 'A','B','C','D','E','F','G','H','I' ];
var wait = 3000;
//Async worker function
function getWorkPromise(item) {
console.log("Starting " + item);
var deferred = Q.defer();
setTimeout(function () {
var status = "Finished " + item;
console.log(status);
deferred.resolve(status);
}, wait);
return deferred.promise;
};
function doAsyncStuffInGroups(arr, n) {
/*
* Process original array into groups, then
* process the groups in series,
* progressing to the next group
* only after performing something asynchronous
* on all group members in parallel.
*/
return arr.map(function(currentValue, i) {
return (i % n === 0) ? arr.slice(i, i+n) : null;
}).filter(function(item) {
return item;
}).reduce(function(promise, group) {
return promise.then(function() {
return Q.all(group.map(function(item) {
return getWorkPromise(item);
}));
});
}, Q());
}
doAsyncStuffInGroups(work_items, 2).then(function() {
console.log("All done");
});
See fiddle. Delay of 3s gives you time to appreciate what's going on. I found 1s too quick.
Solutions like this are elegant and concise but pretty well unreadable. In production code I would provide more comments to help whoever came after me.
For the record:
- The opening
arr.map(...).filter(...)
processes arr
(non destructively) into an array of arrays, each inner array representing a group of length n (plus terminal remainders).
- The chained
.reduce(...)
is an async "serializer" pattern.
- The nested
Q.all(group.map(...))
is an async "parallelizer" pattern.
回答2:
The .then
function of a promise does not mutate the promise, so when you do:
p.then(function(){
// stuff
});
You do not change the promise p
at all, instead, you need to assign it to something:
p = p.then(....)
This is why your queue
promise was always resolved, it never changed beyond Q()
.
In your case, something like changing:
queue.then(Q.all(unit));
Into:
queue = queue.then(function(){ return Q.all(unit); });
Or in ES6 promises and libraries that use their syntax like Bluebird the other answer mentioned:
queue = queue.then(function(){ return Promise.all(unit); });
回答3:
The thing that confused me most is that the async function being chained needs to return a function that returns a promise. Here's an example:
function setTimeoutPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function foo(item, ms) {
return function() {
return setTimeoutPromise(ms).then(function () {
console.log(item);
});
};
}
var items = ['one', 'two', 'three'];
function bar() {
var chain = Promise.resolve();
for (var i in items) {
chain = chain.then(foo(items[i], (items.length - i)*1000));
}
return chain.then();
}
bar().then(function () {
console.log('done');
});
Notice that foo returns a function that returns a promise. foo() does not return a promise directly.
See this Live Demo
回答4:
i would suggest you use bluebird, its the best performance promise out there, https://github.com/petkaantonov/bluebird
the example to chain should also be here https://github.com/petkaantonov/bluebird#how-do-long-stack-traces-differ-from-eg-q