-
-
Save jakearchibald/199f4e44880aa07c0b78f025238d14ed to your computer and use it in GitHub Desktop.
This file contains 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
async function performFetch() { | |
startSpinner(); | |
try { | |
const response = await fetch(url); | |
displayData(await response.json()); | |
} | |
catch(err) { | |
if (err.name != 'AbortError') displayError(); | |
} | |
stopSpinner(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For
fetch
, there isn't much that can be done beyond passing e.g. a FetchController tofetch()
. The other alternative, of returning a controller as the output offetch
would lead to problems when users writefetch(url).then(....)
.So if you're interested immediately into how to solve fetch cancellation, I'd say you're on the right track with passing an additional argument to
fetch()
, because of backward compatibility with its Promise-based API.That said, I want to talk about the broader topic of async Browser APIs.
There has to be a separation between async task definition and async task execution. https://github.com/rpominov/fun-task explains the issue properly, but it's also something known in RxJS.
Task definitions are composed together, but no execution has taken place yet. You can spawn an execution from the definition, and from that point onwards you usually have some object that represents the ongoing execution. For Observables, this is
subscription
. It's only job is to represent the ongoing execution of some task. It has a methodunsubscribe
attached to it. In Fun-task, the return ofrun
is thecancel
fn, so it forgoes the need for an object, but the idea is the same.So, in summary:
That explains why Promise-based APIs have problems with cancellation because:
.then()
) Promises-as-tasks while avoiding their executionWhat can be done for async browser APIs with what the platform offers today? Suggestion: allow representing definition and composition separately from execution. Async task definition can happen in the form of an object (e.g. URL string or Request), then the executor of that task can be a function like
const execution = run(definition)
, and then you can "control" the execution through methods attached, likeexecution.cancel()
. Or evenconst cancel = run(definition)
Notice how callbacks offer us a way of defining tasks and even composing tasks without executing anything yet. This is callback hellp, but at least it only does async task definition and composition:
Because
getMyData()
is never called, this never executes. And we were able to compose with other tasks likegetMoreData
andgetSomeMoreData
.Promises don't have this property. As soon as you construct a Promise, it is on its way towards execution already (in the next tick, though).
Of course callbacks don't yet have cancellation
const cancel = run(getMyData)
, but they aren't far from providing that either. All you need to do is have a contract in place where each callback should synchronously return acancel
function. So then, when composing the callbacks, you can call cancel of each one. Take this idea further, to provide a better API, and soon you'll have reinvented RxJS.So I'll I'm saying is even though callbacks are prone to "callback hell", they are a better lower-level abstraction than Promises for async because they provide this ability to separate definition/composition from execution. And eventually, if/when Observables are in the platform, we could easily wrap callback-based APIs into a better API. This is how RxJS uses callback-based XHR for its Ajax helpers, because XHR at least provides the notion of
run
(xhr.send) as well ascancel
(xhr.abort).As for
fetch
, gotta go with what is in whatwg/fetch#447.