Nawaaz Kortiwala
Nawaaz Kortiwala

Reputation: 21

Promise rejection works differently between then-catch chaining and then(resolveCallback, rejectCallback)

Summary

This code represents a functionality that executes an array of promises and returns their resolved values in the order they were resolved. It also includes a limiter that restricts the number of promises that can be resolved from the given promise array.

Constraints

If the limiter exceeds the length of the promise array, throw a rejection. If any of the promises throws an error, throw a rejection. If the number of resolved promises reaches the limiter value, return the resolved promises in the order of their resolution and ignore the rest of the promises.

Problem

I have two different versions of the code:

v1 - implements a then-catch chain to execute the functionality. v2 - implements a then with the resolve and reject callbacks as two parameters. In the case of v1, the promises resolve without throwing a rejection even if a promise in this promise list rejects.

However, in the case of v2, the code works as expected, i.e., it throws a rejection when any of the promises rejects.

Code

v1

async function somePromises(promises, promisesToWait) {
  // Throws error if promises to wait for is greater than promises in the `promise` array
  if (promisesToWait > promises.length) {
    return Promise.reject("Count is greater than the number of promises");
  }

  return await new Promise((resolve, reject) => {
    const resolvedPromiseValues = []; // stores the values of resolved values
    let hasRejected = false; // tracks the rejection

    promises.forEach((promise) => {
      promise()
        .then((resolvedValue) => {
          // Does not execute if rejected. SOMEHOW, NOT WORKING in v1
          if (hasRejected) return;

          if (resolvedPromiseValues.length < promisesToWait) {
            resolvedPromiseValues.push(resolvedValue);

            if (resolvedPromiseValues.length === promisesToWait) {
              resolve(resolvedPromiseValues);
            }
          }
        })
        .catch((error) => {
          if (!hasRejected) {
            hasRejected = true;
          }

          reject(error);
        });
    });
  });
}

const rejectablePromises = [
  () => Promise.resolve(1),
  () => Promise.resolve(2),
  () => Promise.reject("I will reject"),
  () => Promise.resolve(3),
];

async function main() {
  somePromises(rejectablePromises, 3).then(console.log).catch(console.error);
}

main();

// Expected output: I will reject
// Actual output: [1, 2, 3]


v2

async function somePromises(promises, promisesToWait) {
  // Throws error if promises to wait for is greater than promises in the `promise` array
  if (promisesToWait > promises.length) {
    return Promise.reject("Count is greater than the number of promises");
  }

  return await new Promise((resolve, reject) => {
    const resolvedPromiseValues = []; // stores the values of resolved values
    let hasRejected = false; // tracks the rejection

    promises.forEach((promise) => {
      promise().then(
        (resolvedValue) => {
          // Should not execute if rejected. WORKS AS EXPECTED in v2
          if (hasRejected) return;

          if (resolvedPromiseValues.length < promisesToWait) {
            resolvedPromiseValues.push(resolvedValue);

            if (resolvedPromiseValues.length === promisesToWait)
              resolve(resolvedPromiseValues);
          }
        },
        (error) => {
          if (!hasRejected) {
            hasRejected = true;
          }

          reject(error);
        }
      );
    });
  });
}

const rejectablePromises = [
  () => Promise.resolve(1),
  () => Promise.resolve(2),
  () => Promise.reject("I will reject"),
  () => Promise.resolve(3),
];

async function main() {
  somePromises(rejectablePromises, 3).then(console.log).catch(console.error);
}

main();

// Expected output: I will reject
// Actual output: I will reject

Is there a difference between these two of implementing promises? I read the docs but couldn't find any clarity between the two. Asked ChatGPT, it says the just a matter of readability.

What am I missing?

Upvotes: 0

Views: 82

Answers (1)

Kaiido
Kaiido

Reputation: 137094

There are 2 main differences:

  • .then(a).catch(b) will handle the errors thrown during the execution of a, while .then(a, b) wouldn't:

Promise.resolve(1)
  .then(() => { throw "bad" })
  .catch((err) => console.log("caught in .catch()"));
Promise.resolve(1)
  .then(() => { throw "bad" }, (err) => console.log("caught in .then()"));

  • .then(a).catch(b) will span over two microtasks: the one from then() and the one from .catch():

// The Promises's microtasks will be interleaved with these ones
queueMicrotask(() => {
  console.log("microtask 1");
  queueMicrotask(() => {
    console.log("microtask 2");
    queueMicrotask(() => {
      console.log("microtask 3");
    });
  });
});
Promise.reject("bad")
  .then(() => { })
  .catch((err) => console.log("caught in .catch()"));
Promise.reject("bad")
  .then(() => { }, (err) => console.log("caught in .then()"));

While the first difference is probably something you want to keep in mind, your issue arises from the second one. Since all your Promises are already settled, when using .then().catch() you will pick only the resolved ones and move the rejected ones at the end of the list, and actually ignoring it in your example.

Upvotes: 2

Related Questions