Taken literally, Bluebird's Promise.prototype.map returns immediately.
When doing so, it returns a Promise that may not resolve immediately. I imagine you really want to know how that promise behaves and there are a few things to break down here:
If any of the first 3 promises are rejected, will the other 7 be even started?
Yes. Promises are "started" (that is, scheduled) when you create them. The other 7 will attempt to resolve or are likely enough to do so that you need to assume they will.
Imagine if the browser only allows 4 HTTP connections to a server and you make 10 requests. Those first (failing) 3 will be sent, along with a friend, who may not fail but will certainly run.
You should assume that all promises will invoke their bodies.
It only states that if all fulfill, then the inner promises are awaited, but it's not clear to me what happens if any of them is rejected.
That's easy enough to test:
const Promise = require('bluebird');
function delayReject(delay, err) {
return new Promise((res, rej) => {
console.log('waiting to reject', err);
setTimeout(() => rej(err), delay);
});
}
function delayValue(delay, val) {
return new Promise((res, rej) => {
console.log('waiting to resolve', val);
setTimeout(() => res(val), delay);
});
}
const promises = [1, 2, 3, 4, 5, 6, 7, 8, 9].map(it => {
if (it % 3 === 0) {
return delayReject(50, it);
} else {
return delayValue(50, it);
}
});
Promise.map(promises, v => {
console.log('mapping', v);
return -v;
}).then(it => {
console.log('mapped', it);
}).catch(err => {
console.log('error', err);
});
My output, with node v6.8.1, is:
ssube@localhost ~/questions/40619451 $ > node early-exit.js
waiting to resolve 1
waiting to resolve 2
waiting to reject 3
waiting to resolve 4
waiting to resolve 5
waiting to reject 6
waiting to resolve 7
waiting to resolve 8
waiting to reject 9
mapping 1
mapping 2
error 3
As you may expect, all the promises are scheduled and run, but the map does stop running against them after the first failure.
The Bluebird docs mention that:
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. This doesn't mean that the result array has items in random order, it means that .map can be used for concurrency coordination unlike .all.
That suggests that the order of mapped items may not be persisted, like it is in the above example. We can test that by adding some noise to the delay:
const Promise = require('bluebird');
function delayNoise(n) {
return n + Math.floor(Math.random() * 50);
}
function delayReject(delay, err) {
return new Promise((res, rej) => {
console.log('waiting to reject', err);
setTimeout(() => rej(err), delayNoise(delay));
});
}
function delayValue(delay, val) {
return new Promise((res, rej) => {
console.log('waiting to resolve', val);
setTimeout(() => res(val), delayNoise(delay));
});
}
const promises = [1, 2, 3, 4, 5, 6, 7, 8, 9].map(it => {
if (it % 3 === 0) {
return delayReject(50, it);
} else {
return delayValue(50, it);
}
});
Promise.map(promises, v => {
console.log('mapping', v);
return -v;
}).then(it => {
console.log('mapped', it);
}).catch(err => {
console.log('error', err);
});
Running that yields far more interesting results: if the first promise rejects, then the map ends and does not attempt to map the others. It does short-circuit, as you guessed.
.map()function does not take an array of functions to invoke. It takes an array of pre-existing promises and simply attaches a.then()handler to them. So by the time you're calling Bluebird's.map()function, you've already "started" all of those promises. - idbehold