Skip to content

Instantly share code, notes, and snippets.

@chrisdickinson
Created July 31, 2015 19:32
Show Gist options
  • Save chrisdickinson/a16daec6c53e384d221c to your computer and use it in GitHub Desktop.
Save chrisdickinson/a16daec6c53e384d221c to your computer and use it in GitHub Desktop.
// 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