Created
July 31, 2015 19:32
-
-
Save chrisdickinson/a16daec6c53e384d221c to your computer and use it in GitHub Desktop.
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
// my problem: (with a bit of handwaving here — imagine that each step is | |
// _actually_ async) | |
function doAThing() { | |
return new Promise(resolve => { // A | |
setTimeout(resolve, 100, Math.random()) | |
}).then(randomNum => { // B (derived from A's result) | |
return randomNum * Math.random() | |
}).then(superRandomNumber => { // C | |
return superRandomNumber * originalRandomNumber | |
// where "originalRandomNumber" === "randomNum" from A | |
}) | |
} | |
// solution: pull to outer scope: | |
function doAThing() { | |
var originalRandomNumber = null | |
return new Promise(resolve => { // A | |
setTimeout(resolve, 100, Math.random()) | |
}).then(randomNum => { // B (derived from A's result) | |
originalRandomNumber = randomNum // yoink! | |
return randomNum * Math.random() | |
}).then(superRandomNumber => { // C | |
return superRandomNumber * originalRandomNumber | |
}) | |
} | |
// what I don't like: | |
// * "originalRandomNumber" is a stateful var, may be `null` or `randomNum` | |
// depending on point in time | |
// * "it's okay if you just don't look at originalRandomNumber before step | |
// B" is not a strong guarantee that no one in the future will know not | |
// to do that. | |
// * frontloads context that doesn't matter to the reader, or is actively | |
// misleading, until (potentially) much later | |
// * step B now *has* to be nested in the same wrapping function as step C, | |
// which is a minor tragedy. | |
// | |
// what I like: | |
// * the next programmer can add a step mid-chain without setting fire | |
// to the world | |
// * that's about it, really | |
// solution: splat! | |
function doAThing() { | |
return new Promise(resolve => { // A | |
setTimeout(resolve, 100, Math.random()) | |
}).then(randomNum => { // B (derived from A's result) | |
return Promise.all([ // B is now a function that returns | |
randomNum * Math.random(), // multiple async results instead of one | |
randomNum | |
]) | |
}).then(([superRandomNumber, originalRandomNumber]) => { // C | |
return superRandomNumber * originalRandomNumber | |
}) | |
} | |
// what I don't like: | |
// * reliance on positional args means that someone modifying B's return value | |
// has to be very careful to make sure to modify C's signature | |
// * put another way, the functions have to "know" each other | |
// * reliance on ES20XX features means it's pretty cumbersome in ES5, either | |
// requiring a tuple value to be manually, laboriously unpacked, or a | |
// second function to be created via `_.spread(fn)`. | |
// * step B no longer just performs "step B", it performs "step B plus a | |
// little dance to make step C work" | |
// | |
// what I like: | |
// * no implicit state machine variables! | |
// * each step can be airlifted out of the wrapping function and potentially | |
// reused (though this is subverted by the fact that the functions have to | |
// know about each other and thus it's maybe unwise to bring them out into | |
// a larger context.) | |
// solution: props (and props to @maybekatz for the suggestion) | |
function doAThing() { | |
return new Promise(resolve => { // A | |
setTimeout(resolve, 100, Math.random()) | |
}).then(randomNum => { // B (derived from A's result) | |
return Promise.props({ // B is now a function that returns | |
superRandom: randomNum * Math.random(), // a single context object | |
originalRandom: randomNum | |
}) | |
}).then(({superRandom, originalRandom}) => { // C | |
return superRandom * originalRandom | |
}) | |
} | |
// what I don't like: | |
// * again, step B no longer does "just step B," it does "step B + make step C work" | |
// * to a lesser extent it shares some problems with "splat" — if one changes a prop | |
// returned from step B, one has to be very careful to change the corresponding | |
// part of step C's signature | |
// | |
// what I like: | |
// * order no longer matters, folks can modify step B to add an additional | |
// dependence in any ole place, and step C never needs to be the wiser | |
// * each step can be airlifted out, + it is a little more feasible to do | |
// so, since you can define & document up front the "shape" of the context | |
// object the functions should be talking via | |
// * admittedly we come full circle here: that context object will have | |
// properties that are set to null up until all of the information we | |
// need is available to populate them with a promise |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment