Last active
September 30, 2017 23:04
-
-
Save joedski/424ffa38d769f20c4a46dd554a49f6ca to your computer and use it in GitHub Desktop.
comp-undo-steps 4: All.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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