Last active
September 18, 2021 09:19
-
-
Save mjackson/fafef431758f45ba3b830ad9cbeb2328 to your computer and use it in GitHub Desktop.
A workaround for the lack of a promise cancelation API
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
/** | |
* Registers the given callback to be called in the node.js callback | |
* style, with error as the first argument, when the promise resolves. | |
* | |
* Also, returns a function that may be used to prevent the callback | |
* from ever being called. Calling the returned function is synonymous | |
* with saying "I no longer care about the resolution of this promise, | |
* even if it fails." | |
* | |
* Since there is no provision in the promise spec for cancel/abort | |
* behavior, this may be used as a workaround. | |
* | |
* In React components, you can use this function as a workaround for | |
* the deprecation of the isMounted() feature. | |
* | |
* const AsyncInput = React.createClass({ | |
* handleResponse(error, value) { | |
* this.setState(...) | |
* }, | |
* handleChange(event) { | |
* this.ignoreResponse = createBinding( | |
* makeRequest(event.target.value), | |
* this.handleResponse | |
* ) | |
* }, | |
* componentWillUnmount() { | |
* // Ignore any requests that are currently in progress. | |
* if (this.ignoreResponse) | |
* this.ignoreResponse() | |
* }, | |
* render() { | |
* return <input onChange={this.handleChange}/> | |
* } | |
* }) | |
*/ | |
export const createBinding = (promise, callback) => { | |
let isIgnored = false | |
promise.then( | |
value => !isIgnored && callback(null, value), | |
error => !isIgnored && callback(error) | |
) | |
return () => | |
isIgnored = true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I disagree, @rnicholus. A lot of people share this idea (I've heard it a few times before) so please allow me to expand a little on why I disagree.
tl;dr: Our job here is to handle errors thrown by the promise, not the callback.
The whole promise paradigm is designed to model JavaScript's native
try
/catch
behavior with an asynchronous call "stack". For purposes of illustration, let's assume thatpromise
is a synchronous operation, modeled with a function that eitherreturn
s orthrow
s. If this were the case, my version ofcreateBinding
would look something like this:This is fairly straightforward:
try
to callcallback
with the return value of the operation, butcatch
theerror
and use it as the first argument if the operation fails. One key characteristic of this code is that it makes no attempt tocatch
any errors thatcallback
itself mightthrow
. That's not its job. It's only supposed to resolve thepromise
d value.In the version you propose, with the second
.catch
chained onto the end of the first.then
, the sync equivalent would be:The
.catch
in your example essentially says "catch everything, both errors in promise() and in callback()". We now have the following problems:catch
block, how do you know where theerror
came from? Was it thrown bypromise()
? Or somewhere insidecallback()
?callback
is the culprit:throw
again when we call it in thecatch
block?callback
twice? We're calling it for the 2nd time inside thecatch
.These are semantics that we need to communicate to consumers if we're going to
catch
errors thatcallback
throws.