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.
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');
}
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With