17

If I have two promises A and B, only one of which will succeed, how can I get whichever one fulfills successfully? I'm looking for something similar to Promise.race, but which will return only the first promise that fulfills. I'm using promises from ES6.

Marc Bacvanski
  • 1,458
  • 4
  • 17
  • 33
  • 2
    You are looking for `Promise.any` ([Bluebird](http://bluebirdjs.com/docs/api/promise.any.html), [Creed](https://github.com/briancavalier/creed#any--iterable-promise-e-a--promise-e-a), [WinJS](https://msdn.microsoft.com/en-us/library/windows/apps/br229660.aspx), …) – Bergi Oct 09 '16 at 13:23

5 Answers5

25

Invert the polarity of the promises, and then you can use Promise.all, because it rejects on the first rejected promise, which after inversion corresponds to the first fulfilled promise:

const invert  = p  => new Promise((res, rej) => p.then(rej, res));
const firstOf = ps => invert(Promise.all(ps.map(invert)));

// Utility routines used only in testing.
const wait    = ms => new Promise(res => setTimeout(() => res(ms), ms));
const fail    = f  => Promise.reject(f);
const log     = p  => p.then(v => console.log("pass", v), v => console.log("fail", v));

// Test.
log(firstOf([wait(1000), wait(500) ]));
log(firstOf([wait(1000), fail("f1")]));
log(firstOf([fail("f1"), fail("f2")]));

This will return the value of the first fulfilled promise, or if all reject, an array of rejection reasons.

  • 3
    Clever. This isn't the first time the concept of inverting shows to be useful in promise-land. – jfriend00 Oct 09 '16 at 14:26
  • @jfriend00 Right, this technique was shown in at least one other answer on SO that I can't track down, not original. –  Oct 09 '16 at 16:41
  • 1
    This is a clever hack. – Redu Oct 09 '16 at 17:53
  • @torazaburo, you've possibly seen [this answer](http://stackoverflow.com/questions/21243378/execute-multiple-tasks-asynchronously-and-return-first-successful-result-in-java/21273983#21273983) – Roamer-1888 Oct 10 '16 at 00:50
  • Can also be written as `Promise.prototype.invert()` and `Promise.any()` - https://jsfiddle.net/akvewbje/ – Roamer-1888 Oct 10 '16 at 01:18
7

ES2021 / ES12 - Promise.any

Promise.any - first fulfilled Promise wins.

const promiseA = Promise.reject();
const promiseB = new Promise((resolve) => setTimeout(resolve, 100, 'succeed'));

const promises = [promiseA, promiseB];


Promise.race(promises).then((value) => console.log(value)); // rejected promise

Promise.any(promises).then((value) => console.log(value)); // "succeed"

Notice that any is ignoring the first rejected promise - promiseA because promiseB is being resolved

If all of the given promises are rejected, then the returned promise is rejected.


This is finished proposal and it's scheduled for ES2021 (expected to be released in June 2021)

t_dom93
  • 10,226
  • 1
  • 52
  • 38
4

If you want the first promise that resolves successfully and you want to ignore any rejections that come before that, then you can use something like this:

// returns the result from the first promise that resolves
// or rejects if all the promises reject - then return array of rejected errors
function firstPromiseResolve(array) {
    return new Promise(function(resolve, reject) {
        if (!array || !array.length) {
            return reject(new Error("array passed to firstPromiseResolve() cannot be empty"));
        }
        var errors = new Array(array.length);
        var errorCntr = 0;
        array.forEach(function (p, index) {
            // when a promise resolves
            Promise.resolve(p).then(function(val) {
                // only first one to call resolve will actually do anything
                resolve(val);
            }, function(err) {
                errors[index] = err;
                ++errorCntr;
                // if all promises have rejected, then reject
                if (errorCntr === array.length) {
                    reject(errors);
                }
            });
        });
    });
}

I don't see how you can use Promise.race() for this because it simply reports the first promise to finish and if that first promise rejects, it will report a rejection. So, it is not doing what you asked in your question which is to report the first promise that resolves (even if some rejections finished before it).

FYI, the Bluebird promise library has both Promise.some() and Promise.any() which can handle this case for you.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • You should use `p.then(…, …)` instead of `p.then(…); p.catch(…)` to avoid unhandled rejections – Bergi Oct 09 '16 at 13:18
  • @Bergi -Where is there an unhandled rejection? – jfriend00 Oct 09 '16 at 13:46
  • 2
    If `p` rejects, the result of `p.then(…)` is a promise that is rejected and never handled. – Bergi Oct 09 '16 at 13:49
  • @Bergi - Uh, it's handled by `p.catch()` just fine in the context of this code. What actual problem could occur in this code? Unless you think calling `resolve(val)` could throw, I don't see an unhandled rejection here. Or are you just referring to a promise library might issue a warning (even though there is no actual problem in the code)? – jfriend00 Oct 09 '16 at 13:59
  • 2
    I said *result*. In `q = p.then(…); p.catch(…)` the rejection of `p` is handled (twice), yes, but the rejection of `q` is never handled and as such globally reported, which is wrong. – Bergi Oct 09 '16 at 14:01
  • @Bergi - Except there is no way that `q` can reject in this code unless `resolve(val)` could throw. I guess you're making a theoretical observation, not one that could actually happen. I can chain the `.catch()`. – jfriend00 Oct 09 '16 at 14:02
  • No, it's a very practical problem. Just try to run it in an ES7 environment. `q` will reject if `p` rejects, the handler isn't even invoked. No, please don't use the `p.then(…).catch(…)` antipattern, just do `p.then(…, …)` – Bergi Oct 09 '16 at 14:05
  • You still have not explained how this is a real problem. Or why `p.then(fn1).catch(fn2)` is an anti-pattern. In my book, `p.then(fn1).catch(fn2)` is generically safer than `p.then(fn1, fn2)`, because it catches exceptions in fn1. And, what does ES7 have to do with this? You're making some unexplained objections. – jfriend00 Oct 09 '16 at 14:09
  • 1
    ES7 specifies how unhandled rejections work, and they did incorrectly fire with your original code. Your book is right about `.then(…).catch(…)`, but it says *generically*, and this is a specific situation where we want `.then(…, …)` (we never want `fn1` *and* `fn2` to be called both) - it's not totally wrong as your `fn1` never throws, but it is cleaner, more efficient and would properly report if you made a mistake (like a typo) in `fn1`. – Bergi Oct 09 '16 at 14:16
  • @Bergi - Can you point to some article about what changes for unhandled rejections in ES7 and how that was relevant here? I changed the code to `.then(fn1, fn2)` though it seems like it was for theoretical objections only, not something that could actually occur in this code. – jfriend00 Oct 09 '16 at 14:32
  • Dunno whether there's a good article, it's just that ES7 codifies how/which unhandled rejections are reported to the host environment (and ES6 didn't). Nothing really changed for libraries that already supported it. A web search pointed me to https://github.com/domenic/unhandled-rejections-browser-spec/ though – Bergi Oct 09 '16 at 14:38
  • Another problem I just came across: this doesn't work on empty arrays, it would produce a forever hanging promise instead of rejecting immediately. – Bergi Feb 06 '17 at 17:26
1

I had the same question and gave it a go. You learn a lot by trying these problems yourself!

  1. The accepted answer is very elegant but uses Promise.all which takes the fun for someone learning Promises; also a bit hard to follow imo.
  2. jfriend00's answer is similar to mine but has that logic that goes beyond the Promises fundamentals which is most important here.

I have made use of those nice helper functions present on the accepted answer:

function firstPromise(promiseL, promiseR) {
    return new Promise((resolve, reject) => {
        promiseL.then(l => {
            resolve(l);
        }).catch(error => null);

        promiseR.then(r => {
            resolve(r);
        }).catch(error => null);


        promiseL.catch(errorL => {
            promiseR.catch(errorR => {
                reject(errorL + errorR);
            })
        })
    })
}

const wait = ms => new Promise(res => setTimeout(() => res(ms), ms));
const log = p => p.then(v => console.log("pass", v), v => console.log("fail", v));


log(firstPromise(wait(1000), wait(500)));
log(firstPromise(wait(1000), Promise.reject("Bar")));
log(firstPromise( Promise.reject("Foo"), wait(500)));
log(firstPromise( Promise.reject("Foo"), Promise.reject("Bar")));
cmhteixeira
  • 877
  • 11
  • 22
-1
 //example 1
    var promise_A = new Promise(function(resolve, reject) {
        // выполнить что-то, возможно, асинхронно…
        setTimeout(function(){

            return  resolve(10);
            //return reject(new Error('ошибка'))
        },10000)
    });

    var promise_B = new Promise(function(resolve, reject) {
        // выполнить что-то, возможно, асинхронно…

        setTimeout(function(){
            return  resolve(100);
        },2000)
    });


/*
 //[100,10]
 Promise.all([
  promise_A,promise_B
 ]).then(function(results){
  console.log(results)
 });
*/

 //100
 Promise.race([
  promise_A,promise_B
 ]).then(function(results){
  console.log(results)
 });
zloctb
  • 10,592
  • 8
  • 70
  • 89