How to handle this promise.all case?

124 views Asked by At

We have a parallel promise call:- promise.all([p1,p2]).then().catch() here p1,p2 are 2 different promise calls .

  • p1 - reject for some condition and show e1
  • p2 - reject for some condition and show e2
  • p1 - resolve then do something
  • p2 - do not resolve

p1,p2 reject conditions are different .

If p1,p2 both reject , we show e2

Now suppose both reject but p1 rejects first , how to handle this and show e2 ?

p90 for p1 > p90 for p2 but there can be the edge cases if p1 happens earlier than p2 , hence the query .

Tried using promise.allSettled but it had other issues where p2 executed after the .then block ran for 2 times (there were 2 promise and corresponding array elements in the results ).

We have tried multiple things , resolving with a particular error message in case of p1 failure but it does not work .

1

There are 1 answers

2
T.J. Crowder On

(Preface: Promise terminology can be confusing and most people use it incorrect. My blog post on terminology may be a useful read.)

You were right to look at Promise.allSettled. If you want to do X when one promise is fulfilled and another is rejected, that's the way to go; you get an array of the results, rather than Promise.all's behavior of short-circuiting if any of the input promises is rejected.

p2 - do not resolve

It's not clear what you mean by that, but if you mean you need to do something if p2 doesn't settle (get fulfilled or rejected) within a certain period of time, you probably want to use Promise.race with a timeout promise.

Without code it's hard to help you, but here's an example of combining Promise.allSettled with Promise.race:

class TimeoutError extends Error {}

function timeout(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new TimeoutError("timeout"));
        }, ms);
    });
}

async function test() {
    const p1 = // ...get the first promise...
    const p2 = // ...get the second promise...

    // Wait for the first to settle and the second to either settle or time out
    const [result1, result2] = await Promise.allSettled([
        p1,
        Promise.race([
            p2,
            timeout(150 /* or whatever, value in milliseconds */)
        ])
    ]);

    // See what happened
    if (result1.status === "fulfilled" && result2.status === "fulfilled") {
        // Both promises were fulfilled, see `result1.value` and `result2.value`
    } else if (result1.status === "fulfilled") {
        if (result2.reason instanceof TimeoutError) {
            // The first promise was fulfilled, the second timed out
        } else {
            // The first promise was fulfilled, the second was rejected
        }
    } else if (result2.status === "fulfilled") {
        // First promise was rejected, second was fulfilled
    } else {
        // Both promises were rejected, see `result1.reason` and `result2.reason`
    }
}

Live Example:

function f1(ms, fulfill) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (fulfill) {
                resolve("1 - OK");
            } else {
                reject(new Error("1 - failed"));
            }
        }, ms);
    });
}

function f2(ms, fulfill) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (fulfill) {
                resolve("2 - OK");
            } else {
                reject(new Error("2 - failed"));
            }
        }, ms);
    });
}

class TimeoutError extends Error {}

function timeout(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new TimeoutError("timeout"));
        }, ms);
    });
}

async function example(ms1, fulfill1, ms2, fulfill2, msTimeout) {
    const p1 = f1(ms1, fulfill1);
    const p2 = f2(ms2, fulfill2);

    // Wait for the first to settle and the second to either settle or time out
    const [result1, result2] = await Promise.allSettled([
        p1,
        Promise.race([p2, timeout(msTimeout)])
    ]);

    // See what happened
    if (result1.status === "fulfilled" && result2.status === "fulfilled") {
        console.log(`Both promises were fulfilled:`);
        console.log(`result1.value = ${result1.value}`);
        console.log(`result2.value = ${result2.value}`);
    } else if (result1.status === "fulfilled") {
        if (result2.reason instanceof TimeoutError) {
            console.log(`The first promise was fulfilled, the second timed out`);
            console.log(`result1.value = ${result1.value}`);
            console.log(`result2 was a timeout`);
        } else {
            console.log(`The first promise was fulfilled, the second was rejected`);
            console.log(`result1.value =  ${result1.value}`);
            console.log(`result2.reason = ${result2.reason}`);
        }
    } else if (result2.status === "fulfilled") {
        console.log(`The first promise was rejected, second was fulfilled`);
        console.log(`result1.reason =  ${result1.reason}`);
        console.log(`result2.value = ${result2.value}`);
    } else {
        // Both promises were rejected, see `result1.reason` and `result2.reason`
        console.log(`The first promise was rejected, second was fulfilled`);
        console.log(`result1.reason =  ${result1.reason}`);
        console.log(`result2.rason = ${result2.reason}`);
    }
}

function test(label, ms1, fulfill1, ms2, fulfill2, msTimeout) {
    console.log(`${label}:`);
    example(ms1, fulfill1, ms2, fulfill2, msTimeout)
    .catch((error) => {
        console.error(`Unexpected error in test: ${error.stack ?? String(error)}`);
    });
}

const onClick = (id, fn) => document.getElementById(id).addEventListener("click", fn);

onClick("both-fulfill",     () => test("Both Fulfill", 100, true, 150, true, 200));
onClick("first-fulfills",   () => test("First Fulfills", 100, true, 150, false, 200));
onClick("second-times-out", () => test("Second Times Out", 100, true, 250, true, 200));
onClick("second-fulfills",  () => test("Second Fulfills", 100, false, 150, true, 200));
onClick("both-reject",      () => test("Both Reject", 100, false, 150, false, 200));
<input type="button" id="both-fulfill" value="Both Fulfill">
<input type="button" id="first-fulfills" value="First Fulfills">
<input type="button" id="second-times-out" value="Second Times Out">
<input type="button" id="second-fulfills" value="Second Fulfills">
<input type="button" id="both-reject" value="Both Reject">