7

I need to send a request to multi-servers to see which server will response to my request and if any of them response, I will then further interact with this server. The simplest way is to send my request in sequence, something like this

async function probing(servers) {
    for (const server of servers) {
        const result = await fetch(server)
        if (result.status == 200) {
            return result
        }
    }
}

But I hope to speed up the probing process so I change my code to

async function probing(servers) {
    results = await Promise.all(
        servers.map(async server => {
            return await fetch(server)
        })
    )
    for (const result of results) {
        if (result.status == 200) return result
    }
}

But I still need to await all of promises to finish. What I really need is just if one of them has resolve I then return from my probing()

Is that possbile ?

---- update ----

Thank for the comments promise.any is the solution (and one-liner arrow function can be further simplified as following)

result = await Promise.any(
    servers.map(server => fetch(server))
)

---- update 2 ----

I had thought Promise.any is the way to go and the end of the story. But unfortunately it is not! Promise.any is only available from Chrome 85+ & FF 79+, unlike Promise.all is available for any modern browser except for IE, check here https://v8.dev/features/promise-combinators

And my client needs me to support Chrome version from 2020, i.e. Chrome 80+, I tried to polyfill Promise.any with Babel but I failed.

We use babel6 and I failed to polyfill Promise.any with babel6. I tried to upgraded to babel7 (with npx babel-upgrade --write and some twists) and now the bundled code using Promise.any() can't even work for chrome 88. I asked another question for that How do I polyfill Promise.any() using babel 7?

So now I just have to revert to Promise.all.

---- update 3 ----

I finally made polyfill Promise.any() with Babel 7 work, the key is to using core-js@3 with correct babelrc setting (I am not sure I got them all correct), please refer to my question and answer there.

Qiulang
  • 10,295
  • 11
  • 80
  • 129
  • 2
    You may want to switch to use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race – user1514042 Feb 04 '21 at 10:51
  • 5
    May be [`Promise.any`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) or [`Promise.race`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) will fulfill your requirement. – Nithish Feb 04 '21 at 10:52
  • Thanks! I didn't know promise.race. – Qiulang Feb 04 '21 at 10:53
  • 2
    `Promise.race` will do the job but a fundamental problem with `Promise.race` is, if any of the promises rejects then the whole thing gets rejected. Say if you race 4 promises and promise # 2 rejects prematurelly. This prohibits you from waiting Promises 1, 3 and 4. So.... [There is a clever `Promise.invert` + `Promis.all` solution](https://stackoverflow.com/a/45927525/4543207). I like it very much. – Redu Feb 04 '21 at 11:05
  • It is indeed a clever but hackish way! But I am not sure I want it in my production code. For my own project I will use it. – Qiulang Feb 04 '21 at 11:15
  • @Nithish check my update. I can't use Promise.any – Qiulang Feb 07 '21 at 03:04

1 Answers1

2

In this case Promise.race() looks reasonable but the problem with Promise.race() is any rejecting promise in the race will collapse the whole race. If what we want is to ignore the individual rejections and carry on with the race then we still have an option in which case only if all promises gets rejected we have to perform an action to handle the error.

So if we invent the Promise.invert() and use it with Promise.all() then we get what we want.

var invert = pr => pr.then(v => Promise.reject(v), x => Promise.resolve(x));

Lets see how we can use it;

var invert     = pr  => pr.then(v => Promise.reject(v), x => Promise.resolve(x)),
    random     = r   => ~~(Math.random()*r),
    rndPromise = msg => random(10) < 3 ? Promise.reject("Problem..!!!")
                                       : new Promise((v,x) => setTimeout(v,random(1000),msg)),
    promises   = Array.from({length: 4}, (_,i) => rndPromise(`Promise # ${i}.. The winner.`));

Promise.all(promises.map(pr => invert(pr)))
       .then(e => console.log(`Error ${e} happened..!!`))
       .catch(v => console.log(`First successfully resolving promise is: ${v}`));

So since the promises are inverted now catch is the place that we handle the result while then is the place for eror handling.

I think you can safely use this in production code provided you well comment this code block for anybody reading in future.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • 4
    This is a nice solution from 2017 but now that we have [`Promise.any`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) you should just use that – Bergi Feb 04 '21 at 11:55
  • @Bergi Thanks... Great to know that this functionality is finally implemented. It's was a needed functionality. Though i think the `any` name is a little misnomer. To me `any` works for items in arrays which satisfies the condition in the order of appearance but what we have here is a different abstraction. The last array item can be the first to resolve. I would call it `Promise.first()` had i been consulted. :) – Redu Feb 04 '21 at 17:02
  • @Bergi I failed to polyfill Promise.any for Chrome version lower than 85, please check my updated question. So any suggestion how to do that ? – Qiulang Feb 07 '21 at 02:52
  • 1
    @Bergi I asked a specific question for that here https://stackoverflow.com/questions/66088525/how-do-i-polyfill-promise-any-using-babel-7 – Qiulang Feb 07 '21 at 13:35