Skip to content

Instantly share code, notes, and snippets.

@joedski
Last active September 30, 2017 23:04
Show Gist options
  • Save joedski/424ffa38d769f20c4a46dd554a49f6ca to your computer and use it in GitHub Desktop.
Save joedski/424ffa38d769f20c4a46dd554a49f6ca to your computer and use it in GitHub Desktop.
comp-undo-steps 4: All.
const PLACEHOLDER_ERROR = Symbol('placeholderError');
function deferred() {
const d = {
settled: false, resolved: false, rejected: false,
reason: null, resolution: null,
};
d.promise = new Promise((res, rej) => {
d.resolve = (r) => {
d.settled = true;
d.resolved = true;
d.resolution = r;
res(r);
};
d.reject = (r) => {
d.settled = true;
d.rejected = true;
d.reason = r;
rej(r);
};
});
return d;
}
function all(steps) {
return (next) => async (pctx) => {
const resolutions = new Map();
const rejections = new Map();
const undoRejections = new Map();
const anyRejectionDeferred = deferred();
await Promise.all(steps.map((step) =>
step((stepRes) => {
// Track the resolution before awaiting on any rejection.
// This also means we don't need to worry about returning
// the resolution here.
resolutions.set(step, stepRes);
// If we're the last one resolving, resolve the anyRejectionDeferred.
// No need to check the deferred's state, if we've reached this point
// then nothing rejected.
if (resolutions.size === steps.length) {
anyRejectionDeferred.resolve();
}
// Finally, we await on this promise, which will throw into the
// catch handler below if it's been rejected or rejects later.
// If we resolved it, then this step along with all the others
// will finally resolve at this point.
await anyRejectionDeferred.promise;
})(pctx)
.catch((error) => {
if (resolutions.has(step)) {
if (error[PLACEHOLDER_ERROR]) {
// The Step successfully rolled back and so
// just propagated the error.
return;
}
// This step succeeded, but the error is not the originatingError,
// so the error being caught is from its Undo Process.
undoRejections.set(step, error);
return;
}
// Otherwise, this step did not succeed,
// and the error is from its Do Process.
rejections.set(step, error);
// If the any deferred has not yet been rejected, reject it to trigger
// any waiting and future undos.
if (! anyRejectionDeferred.rejected) {
anyRejectionDeferred.reject({
[PLACEHOLDER_ERROR]: true,
originatingError: error,
});
}
})
));
// Once we get here, everything is either resolved or rejected,
// which is to say, settled.
// Check if anything rejected.
// We don't need to check if any undos rejected because an undo only occurs
// if anything else rejected.
if (rejections.size) {
// TODO: Implement createCompositeErrorFromAll
throw createCompositeErrorFromAll(steps.map((step) =>
undoRejections.get(step)
|| rejections.get(step)
));
}
// Return these in the same order as the steps were put in,
// like Promise.all.
return steps.map((step) => resolutions.get(step));
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment