Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Promise.all rollback successful promises' actions on failure

I am executing multiple promises with the following snippet:

await Promise.all([promise1, promise2, promise3]);

What I would like to achieve is to rollback the effects of the successful promises on the case of a failure from Promise.all(). In more specific terms, this means that the above will do some file encryptions, but if one fails, I would like to delete the other two (or one) files that were encrypted successfully so as to have consistent and clean file groups.

From what I've read this means that I would need two steps: 1. Catching the errors for each promise so that Promise.all() won't throw an error. 2. The puzzling part: Having another Promise.all() sort of:

await Promise.all([rollbackPromise1, rollbackPromise2, rollbackPromise3]);

This one seems to be the tricky part: Should I execute all the rollbacks independent of the promise that failed? This means that I should do another catch for every error such that the Promise.all() waits for every rollback to finish.

Is this the best way to do this, I find it pretty inefficient and ugly in terms of code.

like image 521
Ncifra Avatar asked Oct 20 '25 15:10

Ncifra


2 Answers

You could create your own function implementing the asynchronous call of the functions and performing a rollback if required.

// Function that'll perform a promise.all and rollback if required
async function allWithRollback(promises) {
  // using the map we are going to wrap the promise inside of a new one
  return Promise.all(promises.map(([
    func,
    rollbackFunc,
  ], xi) => ((async() => {
    try {
      await func;

      console.log('One Function succeed', xi);
    } catch (err) {
      console.log('One Function failed, require rollback', xi);

      await rollbackFunc();
    }
  })())));
}

// Call the custom Promise.all
allWithRollback([
  [
    // First param is the promise
    okPromise(),

    // Second param is the rollback function to execute
    () => {},
  ],
  [okPromise(), () => {}],
  [errPromise(), rollback1],
  [errPromise(), rollback2],
  [okPromise(), () => {}],
]);

// ---------

async function okPromise() {
  return true;
}

async function errPromise() {
  throw new Error('no one read this');
}

async function rollback1() {
  console.log('Performed the rollback1');
}

async function rollback2() {
  console.log('Performed the rollback2');
}
like image 152
Orelsanpls Avatar answered Oct 23 '25 05:10

Orelsanpls


This function waits for all promises until 1 fails. Then it calls all rollback functions:

/**
 * Given an array of tuples with a promise and a rollback function that returns
 * a function, `allOrNothing` awaits promises and if any of them fail, calls
 * all rollback functions achieving an "all or nothing" effect.
 */
export async function allOrNothing(
  promisesWithRollbacks: [Promise<unknown>, () => Promise<unknown>][]
): Promise<unknown[]> {
  const promises: Promise<unknown>[] = [];
  const rollbacks: (() => Promise<unknown>)[] = [];
  for (const [promise, rollback] of promisesWithRollbacks) {
    promises.push(promise);
    rollbacks.push(rollback);
  }
  try {
    return Promise.all(promises);
  } catch (err) {
    console.error(err);
    // in case any rollback functions fail, we want others to have time to complete
    return Promise.allSettled(rollbacks);
  }
}

like image 37
Ben Stickley Avatar answered Oct 23 '25 05:10

Ben Stickley



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!