Fetch: Fetch termination API "meeting"

Created on 14 Jan 2017  Â·  21Comments  Â·  Source: whatwg/fetch

Please indicate your availability here: http://doodle.com/poll/2iwihhwrw9tx8kkv.

I suggest we use Hangouts, but I'm open to other options.

Most helpful comment

Summary

Decided

We're looking for something that cancels the request, but also the response if it's in-flight. This means aborting the body stream if the response body is in-flight.

We're dismissing 'tokens' as an API. Although they work well for cancellation, they don't work well for other modifiers like "priority change".

Adding methods to the return value of fetch() feels too risky. The API gets pretty confusing. The 'species' could be a regular promise:

let fetchling = fetch(url);
// Works…
fetchling.cancel();
fetchling = fetch(url).then(r => r.json());
// Does not work, "cancel" is undefined.
fetchling.cancel();

Although this provides an easy way to return a regular promise, it's pretty confusing. If .then created a controllable fetch promise:

let fetchling = fetch(url).then(r => r.json());
// Works as expected:
fetchling.cancel();

fetchling = fetch(url).then(() => fetch(anotherURL));
// Only cancels the first fetch, which is weird:
fetchling.cancel();

const fetches = Promise.all([
  fetch(url1),
  fetch(url2),
  fetch(url3)
]);
// Does not work, "cancel" is undefined
fetches.cancel();

That said, we're not against controlling methods being added to the return value in future.

We're going to move forward with the revealing-function pattern.

fetch(url, {
  control(controller) {
    // ...
  }
});

Where controller will have methods that alter the in-progress fetch, including canceling & later changing priority.

There will be an component that allows the developer to observe the progress of the fetch. This includes changes to priority, progress, final state (complete, cancelled, error etc). This component will include async getters for current state.

The service worker will receive the "observable" component only.

The observable component should use addEventListener rather than observables. We don't want a TC39 dependency, and true observables are likely to be compatible with addEventListener. We should be careful with the naming of this observer as a result.

Less-decided

What does cancellation looks like? I'm pretty sure this will be a promise rejected with a DOMException with name 'AbortError', but we forgot to cover this in the call. It might already be assumed.

What's the link between the controller and observer? Does the controller object extend the observer? Does the controller have the observer as a property? Are the objects entirely separate?

What's the API for the controller/observer? This is the next step, and there have been some proposals already. Some loose requirements:

  • The observable should be async, so it can exist in a different thread to the fetch.
  • It should be reasonably easy to control multiple fetches in the same way.
  • It should be reasonably easy to observe one fetch, and affect one or more other fetches as a result (this is how a service worker fetch event will obey signals from the thing that invoked the fetch).
  • Aborting an already-complete request shouldn't produce an error, it should be a no-op, but there should be some way to confirm that something was actually aborted. E.g. some deletion APIs return a boolean to signal if anything was actually deleted.

All 21 comments

@annevk which timezone?

@delapuente I configured the timezone so it should show up in your local timezone.

The meeting will take place 2017-01-24, 4PM (GMT+01:00). You can still attend, just let me know and I'll share the invite.

Tim O'Ryan, unfortunately I don't have your email address. Please contact me directly so I can add you to the meeting.

To be clear, that'd be 3 PM (15:00) UTC, and 7 AM Pacific time? (Also, to spare newcomers some counting: 2017-01-24 is Tuesday.)

Hey, I would be interested in attending if I'm invited. If I'm not that's also totally fine. I don't want to add in hours so I won't influence the meeting time - let me know when the time is set.

@benjamingr assuming I found the correct email address you got an invite. If not, you need to give me your email address. The time is set as per above.

Thanks! I'll do my best to attend.

Here's the stuff I'd like to get answers for:

  • Are we looking to control/observe the request, or the response too?
  • Should the return value of fetch() provide means to control/observe the fetch?
  • What's the API?

Use-cases to consider:

  • Controlling fetch(url).then(r => r.json()).
  • Controlling multiple parallel fetches.
  • Controlling a series of fetches.
  • fetchEvent.respondWith(fetch(fetchEvent.request)) - does this get me anything for free? If the page aborts the fetch, will the service worker request/response be aborted?
  • What if a service worker response is made of:

    • An unrelated fetch.

    • Multiple fetches.

    • Cache API match(es).

var cancelController;
fetch("/fetch", {
   cancel(cancelFn) { cancelController = cancelFn; }
});
cancelController(); // cancel, returns a promise with true/false

?

var cancelController;
fetch("/fetch", {
   control(controller) { cancelController = controller; }
});
cancelController.cancel(); // cancel, returns a promise with true/false
const fetch = fetch("/fetch");
fetch.cancel(); // cancel, returns a promise with true/false
const readonly = fetch.then();

Other use cases to consider: it would be nice if this mechanism were generic enough to be reused for canceling a stream pipeTo as well.

Summary

Decided

We're looking for something that cancels the request, but also the response if it's in-flight. This means aborting the body stream if the response body is in-flight.

We're dismissing 'tokens' as an API. Although they work well for cancellation, they don't work well for other modifiers like "priority change".

Adding methods to the return value of fetch() feels too risky. The API gets pretty confusing. The 'species' could be a regular promise:

let fetchling = fetch(url);
// Works…
fetchling.cancel();
fetchling = fetch(url).then(r => r.json());
// Does not work, "cancel" is undefined.
fetchling.cancel();

Although this provides an easy way to return a regular promise, it's pretty confusing. If .then created a controllable fetch promise:

let fetchling = fetch(url).then(r => r.json());
// Works as expected:
fetchling.cancel();

fetchling = fetch(url).then(() => fetch(anotherURL));
// Only cancels the first fetch, which is weird:
fetchling.cancel();

const fetches = Promise.all([
  fetch(url1),
  fetch(url2),
  fetch(url3)
]);
// Does not work, "cancel" is undefined
fetches.cancel();

That said, we're not against controlling methods being added to the return value in future.

We're going to move forward with the revealing-function pattern.

fetch(url, {
  control(controller) {
    // ...
  }
});

Where controller will have methods that alter the in-progress fetch, including canceling & later changing priority.

There will be an component that allows the developer to observe the progress of the fetch. This includes changes to priority, progress, final state (complete, cancelled, error etc). This component will include async getters for current state.

The service worker will receive the "observable" component only.

The observable component should use addEventListener rather than observables. We don't want a TC39 dependency, and true observables are likely to be compatible with addEventListener. We should be careful with the naming of this observer as a result.

Less-decided

What does cancellation looks like? I'm pretty sure this will be a promise rejected with a DOMException with name 'AbortError', but we forgot to cover this in the call. It might already be assumed.

What's the link between the controller and observer? Does the controller object extend the observer? Does the controller have the observer as a property? Are the objects entirely separate?

What's the API for the controller/observer? This is the next step, and there have been some proposals already. Some loose requirements:

  • The observable should be async, so it can exist in a different thread to the fetch.
  • It should be reasonably easy to control multiple fetches in the same way.
  • It should be reasonably easy to observe one fetch, and affect one or more other fetches as a result (this is how a service worker fetch event will obey signals from the thing that invoked the fetch).
  • Aborting an already-complete request shouldn't produce an error, it should be a no-op, but there should be some way to confirm that something was actually aborted. E.g. some deletion APIs return a boolean to signal if anything was actually deleted.

We're going to move forward with the revealing-function pattern.

@jakearchibald does this include passing the future FetchController object to the fetch()? I mean:

const control = new FetchController();
const fetching = fetch(request, { control });

@delapuente we need to hash out the details. If you have specific ideas for how it could all work well together, they're most welcome. A somewhat more detailed proposal by @stuartpb can be found at https://github.com/stuartpb/FetchController-spec.

@delapuente I updated the original post at https://github.com/whatwg/fetch/issues/447 with the current state of things.

If there'll be another meeting, I'd like to be included - some time between noon and 7 PM Pacific, if possible (or as close to that as would be feasible).

My feedback on the meeting notes:

  • Everything in "Decided" sounds right, though re: "We're going to move forward with the revealing-function pattern." - any thoughts on also allowing FetchControllers to be preconstructed, in the compatible fashion I described in my FetchController-spec?
  • "What does cancellation looks like?" - what you described there is what I've been working with.
  • "What's the link between the controller and observer?" - in the spec I wrote, a FetchObserver is effectively independent of a FetchController (beyond the fact that it is attached to observing the same fetch as the controller controls). A FetchController always contains a FetchObserver, because it shouldn't be possible to end up in a situation where you can act on a fetch whose status you can't read. Note that, as written, a FetchObserver doesn't "belong" to a FetchController so much as it is intrinsically associated with it, due to the dynamics of how a Fetch may be dispatched (there's no way to start a Fetch with a Controller and an Observer that isn't the one attached to that Controller).
  • "The observable should be async, so it can exist in a different thread to the fetch." It's already got an addEventListener, and I think that's quite async enough. If this statement means to assert that multi-threading should imply all mutable properties to be only obtainable through asynchronous accessors, I will state now that I do not concur with that assertion, and can expand on the reasons in a separate issue.
  • "It should be reasonably easy to control multiple fetches in the same way." - Keeping a collection of FetchControllers and performing operations in a coll.forEach(ctl=>{/*...*/}) seems perfectly reasonable to me.
  • Same goes for affecting a fetch as a result of something happening in an observed fetch - observe.addEventListener('abort',e=>coll.forEach(ctl=>ctl.abort())) seems fine to me.
  • "Aborting an already-complete request shouldn't produce an error, it should be a no-op, but there should be some way to confirm that something was actually aborted." - I was thinking this should be an error, but I'm coming around to the argument here that it should be fire-and-report-if-it-did-anything, since we can't well take events out once another thread's put them in the pipe. I'll expand on this in #448.

@stuartpb thanks for the feedback. Just to clarify, when I wrote things like:

What's the link between the controller and observer?

I don't mean "we don't have a clue", I just mean it's yet to be decided. Your proposals have been read, and are being considered.

Closing this as the meeting happened. I've updated https://github.com/whatwg/fetch/issues/447 and discussion can continue there.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

radubrehar picture radubrehar  Â·  14Comments

kentonv picture kentonv  Â·  9Comments

tabatkins picture tabatkins  Â·  9Comments

annevk picture annevk  Â·  9Comments

jakearchibald picture jakearchibald  Â·  5Comments