There are two ways to handle promise results: . then(), which is called as a callback upon completion of the promise. async / await, which forces the current thread of execution to wait for the promise to complete.
You access the result of a promise by using the . then method (or using await in an async function). Your . then callback is then called when/if the result is made available, which will happen after you call resolve , or if the promise was already resolved prior it will be called rather quickly.
This is a classic example where inverting your logic makes it much clearer. Your "race" in this case is that you want your rejection behavior to in fact be success behavior.
function oneSuccess(promises){
return Promise.all(promises.map(p => {
// If a request fails, count that as a resolution so it will keep
// waiting for other possible successes. If a request succeeds,
// treat it as a rejection so Promise.all immediately bails out.
return p.then(
val => Promise.reject(val),
err => Promise.resolve(err)
);
})).then(
// If '.all' resolved, we've just got an array of errors.
errors => Promise.reject(errors),
// If '.all' rejected, we've got the result we wanted.
val => Promise.resolve(val)
);
}
You can write this quite easily yourself.
function raceToSuccess(promises) {
return new Promise(
resolve =>
promises.forEach(
promise =>
promise.then(resolve)
)
);
}
This kicks off all the promises, and when any succeeds resolves the new promise with its value. Failed promises are ignored. Subsequent successful promises cause nothing to happen, since the new promise has already been resolved. Note that the resulting promise will never resolve or reject if none of the input promises resolve.
Here is a modified version which returns a rejected promise if all of the input promises reject:
function raceToSuccess(promises) {
let numRejected = 0;
return new Promise(
(resolve, reject) =>
promises.forEach(
promise =>
promise .
then(resolve) .
catch(
() => {
if (++numRejected === promises.length) reject();
}
)
)
);
}
I like @loganfsmyth's approach; you should probably upvote it for its conceptual clarity. Here's a variation of it:
function invertPromise(promise) {
return new Promise(
(resolve, reject) =>
promise.then(reject, resolve)
);
}
function raceToSuccess(promises) {
return invertPromise(
Promise.all(
promises.map(invertPromise)));
}
Another idea is to turn the failed promises into promises which neither resolve nor reject (in other words, are permanently pending), then use Promise.race
:
function pendingPromise() { return new Promise(() => { }); }
function killRejected(promise) { return promise.catch(pendingPromise); }
function raceToSuccess(promises) {
return Promise.race(promises.map(killRejected));
}
You may or not like the behavior of this. The returned promise will never fulfill or reject if none of the input promises fulfill. It's also possible that the permanently pending promises will not get GC'd, or some engines might eventually complain about them.
I'm using a function based on Promise.race() but with a twist: it ignores rejects, unless all given promises reject:
// ignores any rejects except if all promises rejects
Promise.firstResolve = function (promises) {
return new Promise(function (fulfil, reject) {
var rejectCount = 0;
promises.forEach(function (promise) {
promise.then(fulfil, () => {
rejectCount++;
if(rejectCount == promises.length) {
reject('All promises were rejected');
}
});
});
});
};
It's based on Rich Harris's Promise polyfill race method. I just made the looping promise reject conditional: it only rejects the main promise, if all given promises failed, otherwise it ignores rejects and resolves the first success.
Usage:
// fastest promise to end, but is a reject (gets ignored)
var promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("foo")
}, 100);
})
// fastest promise to resolve (wins the race)
var promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("bar")
}, 200);
})
// Another, slower resolve (gets ignored)
var promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("baz")
}, 300);
})
Promise.firstResolve([promise1, promise2, promise3])
.then((res) => {
console.log(res) // "bar"
})
.catch(err => {
console.log(err) // "All promises were rejected" (if all promises were to fail)
})
The reason I use this instead of the promise inverting approach, is because in my opinion this is more readable.
To please the question in the strictest way, below there is a version that resolves the first successful promise but doesn't do anything if all given promises fail:
// ignores any and all rejects
Promise.firstResolve = function (promises) {
return new Promise(function (fulfil) {
promises.forEach(function (promise) {
promise.then(fulfil, () => {});
});
});
};
(usage same as above)
Edit: This is in fact the same as @user663031's suggestion. Which I haven't realized until just now.
Is there something in the API that permits a "raceToSuccess" kind of behavior
There is now. There is a finished Stage 4 proposal for Promise.any
:
Promise.any()
takes an iterable of Promise objects and, as soon as one of the promises in the iterable fulfills, returns a single promise that resolves with the value from that promise.
So, the following syntax can be used:
// assume getApi returns a Promise
const promises = [
getApi('url1'),
getApi('url2'),
getApi('url3'),
getApi('url4'),
];
Promise.any(promises)
.then((result) => {
// result will contain the resolve value of the first Promise to resolve
})
.catch((err) => {
// Every Promise rejected
});
Promise.any
has been implemented in all modern browsers. There are some polyfills available too.
Old topic but here's my entry; it's essentially @loganfsmyth's solution, but with a few more checks to conform to conventions established by Promise.all()
:
Promise.any = a => {
return !a.length ?
Promise.resolve() :
Promise.all(a.map(
e => (typeof e.then !== 'function') ?
Promise.reject(e) :
e.then(
result => Promise.reject(result),
failure => Promise.resolve(failure)
)
)).then(
allRejected => Promise.reject(allRejected),
firstResolved => Promise.resolve(firstResolved)
);
};
// Testing...
function delayed(timeout, result, rejected) {
return new Promise((resolve, reject) => {
setTimeout(
() => rejected ? reject(result) : resolve(result),
timeout);
});
}
Promise.any([
delayed(800, 'a'),
delayed(500, 'b'),
delayed(250, 'c', true)
]).then(e => {
console.log('First resolved (expecting b):', e);
});
Promise.any([
delayed(800, 'a', true),
delayed(500, 'b', true),
delayed(250, 'c', true)
]).then(null, e => {
console.log('All rejected (expecting array of failures):', e);
});
Promise.any([
delayed(800, 'a'),
delayed(500, 'b'),
delayed(250, 'c', true),
'd',
'e'
]).then(e => {
console.log('First non-promise (expecting d):', e);
});
// Because this is the only case to resolve synchronously,
// its output should appear before the others
Promise.any([]).then(e => {
console.log('Empty input (expecting undefined):', e);
});
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