Loop through waterfall tasks - blue bird promises

I am trying to accomplish some tasks with bluebird, just using the timeout as an experimental mechanism. [do not use asynchronous 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

 finish, one, two, three, four, five, 

I need the loop to be repeated only after each timeout, as completed, and then run the last one with completion.

+7
javascript promise asynchronous bluebird
source share
1 answer
  • In the function object passed to Promise.map , you need to return the Promise object so that all Promises are allowed, and the array of allowed values ​​can be passed 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 finish executable function is executed, since Promises from Promises.map resolved using undefined s. You can confirm that like this

     ... }).then(function (result) { console.log(result); console.log('finish'); }); 

    will print

     [ undefined, undefined, undefined, undefined, undefined ] finish one two three four five 

    So your code should have a return 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 the code prints things in order, as we return Promise objects, and then the function returned with finish is called after all Promises are resolved. But all of them are not solved sequentially. If this happens, each number will be printed after the specified time. This brings us to the second part.

  • Promise.map will execute the function passed as a parameter as soon as Promises in the array are resolved. Labeling the documentation,

    The matching function for this element is called as soon as possible, that is, when the promise of the index of this element 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 value. Thus, all of them simultaneously wait 500 ms and are immediately solved. This does not happen sequentially.

    Since you want them to be executed sequentially, you need to use Promise.each . Quoting documents,

    The iteration occurs sequentially ..... If the iterator function returns a promise, or then it is possible, the result for the promise waits before continuing with the next iteration.

    Since Promises are created sequentially and permission is expected to continue, the 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 order doesn't matter , as Benjamin Gruenbaum suggested, you can use Promise.map yourself, with a 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 parameter basically limits the number of Promises that can be created and resolved before more promises are created. Thus, in this case, since the limit is 1, he will create the first promise, and upon reaching the limit he will wait until the created promise is resolved before moving on to the next promise.


If the whole point of using calculate is to introduce a delay, 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 bind the allowed Promise value to the next subsequent 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 takes on a dynamic value, you can simply write the same thing 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, it is better to use the .done() method to mark it, for example,

 ... }).done(function () { console.log('finish'); }); 

General note: If you are not going to do any processing in the current function, but you only use it to track progress or to track the process, then you can better change them to Promise.tap . This way your code will become

 Promise.each(Object.keys(fileA), function (key) { return Promise.delay(fileA[key], 500).tap(console.log); }).then(function () { // Do processing console.log('finish'); }); 
+15
source share

All Articles