[whatwg/fetch] Aborting a fetch (#447)

Fetch is lower-level than XHR except for cancellation. We were blocked on the resolution of [cancelable promises](https://github.com/tc39/proposal-cancelable-promises), but consensus has broken down.

We need to answer the following:

* How do we provide an API to abort a fetch?
* What does "abort" look like? Presumably some kind of error.
* Does this API cancel the request only, or does it also abort the response?

We need to consider two use-cases:

```js
const p = fetch(url);
// I want to abort the above fetch.
```

```js
// In a service worker:
addEventListener('fetch', event => {
  // I want to know if/when the browser is no longer interested in a response.
});
```

…although they don't need to be solved by the same solution.

We also need to consider other request-modifying methods, such as changes to priority, a way to observe related HTTP/2 events such as push, and possibly a way to observe upload progress.

# Reacting to cancellation

Since we can't have cancelable promises, that only really leaves us with rejection. There's already a [`DOMException` with name `AbortError`](https://heycam.github.io/webidl/#aborterror).

```js
startSpinner();
fetch(url).catch(err => {
  if (err.name != 'AbortError') {
    showMessage("Oh no it broke!");
  }
}).finally(() => {
  stopSpinner();
});
```

(Hopefully `finally` will make it through TC39).

# Abort API

Possible solutions:

## Add a method to the returned promise

```js
const p = fetch(url);
p.abort(); // works
const p2 = p.then(r => r.clone());
p2.abort(); // Error: abort is not a function
```

If `abort()` only cancels the request, we may want methods like `response.json()` to return a promise with an abort method.

**Pros:**

* Works for other request-modifying methods.

**Cons:**

* May be messy to get `.then()` to return a normal promise.
* Allows the code you pass the promise to to call abort - but the receiving code can already abort/lock the responses stream.
* Doesn't really work for the service worker case, unless we provided something like `fetchEvent.useAbortable(p)` where you pass the abortable object back to the browser. Or the event object contains a way to observe cancelation.

## Controller object

```js
fetch(url, {
  control(controller) {
    controller.abort();
  }
})
```

If `controller.abort()` only cancels the request, we may want methods like `response.json()` to expose their own controller via a similar mechanism.

**Pros:**

* Works for other request-modifying methods.

**Cons:**

* Again, doesn't work for the service worker case.
* You'll likely want to pass the controller up to the parent scope, which is a little messy.

## Tokens

```js
const cancelToken = new CancelToken(cancel => {
  cancel();
});

fetch(url, {
  cancelToken
});
```

```js
// In a service worker:
addEventListener('fetch', event => {
  event.respondWith(
    fetch('cat.jpg', {cancelToken: event.cancelToken})
  );
})
```

If only the request is cancelled (which seems likely with this pattern), methods like `response.json()` will also accept a cancel token, which can be the same one passed to fetch.

**Pros:**

* Works really well with the service worker case.
* Low level, useful for lots of other async functions.
* Easy to cancel multiple things at once.

**Cons:**

* You'll likely want to pass the cancel function up to the parent scope, which is a little messy.
* Current proposal abandoned along with cancelable promises. However, I think Observables want them, so they may come back to life. But this leaves us waiting on TC39 again unless we design our own "DOM cancel tokens", which could land us in trouble if TC39 spec something similar but incompatible.
* Doesn't solve the problem of other request-modifying methods.

## Method on the request object

```js
const request = new Request(url);
fetch(request);
request.abort();
```

```js
// In a service worker:
addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request)
  );
})
```

The fetch spec copies requests internally, so we'd need to make sure calling methods like `abort` is passed onto the copies. This probably wouldn't include copies created with `.clone()`.

**Pros:**

* Works for other request-modifying methods.

**Cons:**

* Only works in the service worker if you're fetching `fetchEvent.request`.
* If this only aborts the request (which kinda makes sense, it's `request.abort()`), we don't have a way to cancel the response.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/fetch/issues/447

Received on Wednesday, 4 January 2017 17:18:18 UTC