I'm looking to loop through some tasks with bluebird, just using timeout as a experimental mechanism. [not looking to use async or any other library]
var Promise = require('bluebird');
var fileA = {
1: 'one',
2: 'two',
3: 'three',
4: 'four',
5: 'five'
};
function calculate(key) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(fileA[key]);
}, 500);
});
}
Promise.map(Object.keys(fileA), function (key) {
calculate(key).then(function (res) {
console.log(res);
});
}).then(function () {
console.log('finish');
});
result is
finish,
one,
two,
three,
four,
five,
I need the loop to only iterate once each timeout as completed, then fire the last thenable with finish.
In the function object passed to Promise.map
, you need to return a Promise object, so that all the Promises will be resolved and the array of resolved values can be passed on to the next then
function. In your case, since you are not returning anything explicitly, undefined
will be returned, by default, not a promise. So, the thenable function with finish
gets executed as the Promises of Promises.map
were resolved with undefined
s. You can confirm that like this
...
}).then(function (result) {
console.log(result);
console.log('finish');
});
would print
[ undefined, undefined, undefined, undefined, undefined ]
finish
one
two
three
four
five
So, your code should have a return
statement like this
Promise.map(Object.keys(fileA), function (key) {
return calculate(key).then(function (res) {
console.log(res);
});
}).then(function () {
console.log('finish');
});
Now, you will see that code prints the things on order, as we return the Promise objects and the thenable function with finish
is invoked after all the Promises are resolved. But they all don't get resolved sequentially. If that had happened, every number would be printed after the specified time elapses. That brings us to the second part.
Promise.map
will execute the function passed as parameter, as soon as the Promises in the array are resolved. Quoting the documentation,
The mapper function for a given item is called as soon as possible, that is, when the promise for that item's index in the input array is fulfilled.
So, all the values in the array are converted to Promises which are resolved with the corresponding values and the function will be called immediately for each and every value. So, they all wait 500 ms at the same time and resolve at once. This doesn't happen sequentially.
Since you want them to execute sequentially, you need to use Promise.each
. Quoting the docs,
Iteration happens serially. .... If the iterator function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration.
Since the Promises are created serially and resolution is awaited before continuing, order of the result is guaranteed. So your code should become
Promise.each(Object.keys(fileA), function (key) {
return calculate(key).then(function (res) {
console.log(res);
});
}).then(function () {
console.log('finish');
});
Additional Note:
If the Order does not matter, as suggested by Benjamin Gruenbaum, you can use Promise.map
itself, with the concurrency
limit, like this
Promise.map(Object.keys(fileA), function (key) {
return calculate(key).then(function (res) {
console.log(res);
});
}, { concurrency: 1 }).then(function () {
console.log('finish');
});
The concurrency
option basically limits the number of Promises which can be created and resolved before creating more promises. So, in this case, since the limit is 1, it will create the first promise and as the limit is reached, it will wait till the created Promise resolves, before moving on to the next Promise.
If the whole point of using calculate
is to introduce delay, then I would recommend Promise.delay
, which can be used like this
Promise.each(Object.keys(fileA), function (key) {
return Promise.delay(500).then(function () {
console.log(fileA[key]);
});
}).then(function () {
console.log('finish');
});
delay
can transparently chain the resolved value of a Promise to the next thenable function, so the code can be shortened to
Promise.each(Object.keys(fileA), function (key) {
return Promise.resolve(fileA[key]).delay(500).then(console.log);
}).then(function () {
console.log('finish');
});
Since Promise.delay
accepts a dynamic value, you can simply write the same as
Promise.each(Object.keys(fileA), function (key) {
return Promise.delay(fileA[key], 500).then(console.log);
}).then(function () {
console.log('finish');
});
If the Promise chain ends here itself, its better to use .done()
method to mark it, like this
...
}).done(function () {
console.log('finish');
});
General Note: If you are not going to do any processing in a thenable function but you are using that just to track the progress or to follow the process, then you can better change them to Promise.tap
. So, your code would become
Promise.each(Object.keys(fileA), function (key) {
return Promise.delay(fileA[key], 500).tap(console.log);
}).then(function () {
// Do processing
console.log('finish');
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With