javascriptnode.jspromise

Javascript resolve first element in array of promises that has been fullfilled in order


I'm doing multiple requests to different APIs (simulated by the delayedPromise function) which each take a certain amount of time to fulfill. If one of them fulfills I return the value it resolves and that's it. I'm currently doing those one after the other since the first request would be the most accurate while the second would be less accurate etc. So if the first request fails and the second fulfills I return the second

To speed things up I thought about parallelizing those requests. To accomplish that I'm creating an array that holds the promises and it awaits the result of the first element and if that fails it awaits the result of the second element etc...

Here's the code:

function promiseTest() {
    return new Promise(function (resolve, reject) {
        let promises = []
        new Promise(async function (resolve, reject) {
            promises.push(delayedPromise(true, 10000, "1")) //If true resolves after 10000 ms; false = reject
            promises.push(delayedPromise(false, 1000, "2"))
            promises.push(delayedPromise(false, 1000, "3"))
            let answer;
            let answer2
            let answer3;
            answer = await promises[0].catch(async err => {
                answer2 = await promises[1].catch(async err => {
                    answer3 = await promises[2].catch(err => { reject(err) })
                })
            })
            if (answer != undefined) {
                resolve(answer)
            }
            else if (answer2 != undefined) {
                resolve(answer2)
            }
            else if (answer3 != undefined) {
                resolve(answer3)
            }

        }).then(res => resolve(res)).catch(err => {reject(err)})

    })
}

function delayedPromise(bool, time,string ="") {
    return new Promise(function (resolve, reject) {
        if (bool) {
            setTimeout(resolve(string), time)
        }
        else {
            setTimeout(reject(), time)
        }
    })
}

The issue is that while this approach works it's

A: Most likely not the best solution and hardly scalable

and B: The exact code above is creating a bunch of uncaught exceptions errors. I believe this is when I receive a result for answer1 I'm not handling any of the rejections the other functions returned.

Is there any better way to do these kinds of things/ fix the above mentioned concerns? I'd appreciate any ideas or improvements! Many thanks in advance

PS: If you can phrase my title better please let me know I'm kind of struggling to convey my issue in one sentence


Solution

  • Code in the question simplifies to:

    function promiseTest() {
        let promises = [
            delayedPromise(true, 10000, '1'),
            delayedPromise(false, 1000, '2'),
            delayedPromise(false, 1000, '3')
        ];
        return promises[0]
        .catch(() => promises[1])
        .catch(() => promises[2]);
    }
    

    As soon as a successful promise is encountered, the chain will stop catching and the successful result will drop all the way through to be delivered (promise-wrpped) to promiseTest's caller.

    The catch-chain could also be written as follows ...

    Promise.reject()
    .catch(() => promises[0])
    .catch(() => promises[1])
    .catch(() => promises[2]);
    

    ... which helps in formulating a proceduralized version for an array of any length, as follows:

    function promiseTest() {
        let promises = [...]; // array of Promises (any length)
        return promises.reduce((p_, p) => p_.catch(() => p), Promise.reject());
    }
    

    Assuming that measures to avoid unhandled rejections are not taken in the formulation of the promises array, you can do so (with a series of "branch-catches") in the reduction ...

    function promiseTest() {
        let promises = [...]; // array of Promises (any length).
        return promises.reduce((p_, p) => {
            p.catch(() => null); // avoid unhandled rejections.
            return p_.catch(() => p);
        }, Promise.reject());
    }