Skip to content

Commit a72d7ca

Browse files
authored
[explainer] Add explainer for fetchLater() API (#80)
This PR adds overview and example codes for the draft `fetchLater()` API spec from whatwg/fetch#1647 The API will address the discussions in #70 #72 #73 #74 #75 #76.
1 parent 9279037 commit a72d7ca

File tree

1 file changed

+144
-1
lines changed

1 file changed

+144
-1
lines changed

docs/fetch-later-api.md

+144-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,147 @@
11
# fetchLater() API
22

3+
*This document is an explainer for fetchLater() API. It is evolved from a series of [discussions and concerns](https://github.com/WICG/pending-beacon/issues/70) around the experimental PendingBeacon API and the draft PendingRequest API.*
34

4-
TODO
5+
[Draft Specification](https://whatpr.org/fetch/1647/094ea69...152d725.html)
6+
7+
## Overview
8+
9+
`fetchLater()` is a JavaScript API to request a deferred fetch. Once requested, the deffered request is queued by the browser, and will be invoked in one of the following scenarios:
10+
11+
* The document is destroyed.
12+
* The document is bfcached and not restored after a certain time.
13+
14+
The API returns a `FetchLaterResult` that contains a boolean field `sent` that may be updated to tell whether the deferred request has been sent out or not.
15+
On successful sending, the whole response will be ignored, including body and headers. Nothing at all should be processed or updated, as the page is already gone.
16+
17+
Note that from the point of view of the API user, the exact send time is unknown.
18+
19+
### Constraints
20+
21+
* A deferred fetch request body, if exists, has to be a byte sequence. Streaming requests are not allowed.
22+
* The total size of deferred fetch request bodies are limited to 64KB per origin. Exceeding this would immediately reject with a QuotaExceeded.
23+
24+
## Key scenarios
25+
26+
### Defer a `GET` request until page is destroyed or evicted from bfcache
27+
28+
No matter the request succeeds or not, the browser will drop the resonse or
29+
error from server, and the caller will not be able to tell.
30+
31+
```js
32+
fetchLater('/send_beacon');
33+
```
34+
35+
### Defer a `POST` request until around 1 minute after page is bfcached
36+
37+
> **NOTE**: **The actual sending time is unkown**, as the browser may wait for a longer or shorter period of time, e.g., to optimize batching of deferred fetches.
38+
39+
```js
40+
fetchLater({
41+
url: '/send_beacon'
42+
method: 'POST'
43+
body: getBeaconData(),
44+
}, {backgroundTimeout: 60000 /* 1 minute */});
45+
```
46+
47+
### Send a request when page is abondoned
48+
49+
```js
50+
let beaconResult = null;
51+
52+
function createBeacon(data) {
53+
if (beaconResult && beaconResult.sent) {
54+
// Avoid creating duplicated beacon if the previous one is still pending.
55+
return;
56+
}
57+
58+
beaconResult = fetchLater(data, {backgroundTimeout: 0});
59+
}
60+
61+
addEventListener('pagehide', () => createBeacon(...));
62+
addEventListener('visibilitychange', () => {
63+
if (document.visibilityState === 'hidden') {
64+
// may be the last chance to beacon, though the user could come back later.
65+
createBeacon(...);
66+
}
67+
});
68+
```
69+
70+
### Update a pending request
71+
72+
```js
73+
let beaconResult = null;
74+
let beaconAbort = null;
75+
76+
function updateBeacon(data) {
77+
const pending = !beaconResult || !beaconResult.sent;
78+
if (pending && beaconAbort) {
79+
beaconAbort.abort();
80+
}
81+
82+
createBeacon(data);
83+
}
84+
85+
function createBeacon(data) {
86+
if (beaconResult && beaconResult.sent) {
87+
// Avoid creating duplicated beacon if the previous one is still pending.
88+
return;
89+
}
90+
91+
beaconAbort = new AbortController();
92+
beaconResult = fetchLater({
93+
url: data
94+
signal: beaconAbort.signal
95+
});
96+
}
97+
```
98+
99+
### Implement `PendingBeacon` with `fetchLater()`
100+
101+
The following implementation try to simulate the behavior of [`PendingBeacon` API](pending-beacon-api.md#javascript-api) from earlier proposal.
102+
103+
```js
104+
class PendingBeacon {
105+
#abortController = null;
106+
#requestInfo = null;
107+
#backgroundTimeout = null;
108+
#result = null;
109+
110+
constructor(requestInfo, backgroundTimeout) {
111+
this.#requestInfo = requestInfo;
112+
this.#backgroundTimeout = backgroundTimeout;
113+
this.#schedule();
114+
}
115+
116+
// Schedules a deferred request to send on page destroyed or after page in bfcached + `this.#backgroundTimeout` time.
117+
#schedule() {
118+
if (this.#result && this.#result.sent) {
119+
this.#abortController = null;
120+
}
121+
if (this.#abortController) {
122+
// Cacnel previous pending request.
123+
this.#abortController.abort();
124+
}
125+
126+
this.#abortController = new AbortController();
127+
this.#requestInfo.signal = this.#abortController.signal;
128+
#result = fetchLater(this.#requestInfo, {this.#backgroundTimeout});
129+
}
130+
131+
// Aborts the deferred request and schedules a new one.
132+
update(requestInfo) {
133+
this.#requestInfo = requestInfo;
134+
this.#schedule();
135+
}
136+
137+
// sendNow(): User should directly call `fetch(requestInfo)` instead.
138+
}
139+
```
140+
141+
## Open Discussions
142+
143+
See [Deferred fetching PR](https://github.com/whatwg/fetch/pull/1647).
144+
145+
## Relevant Discussions
146+
147+
See [the fetch-based-api hotlist](https://github.com/WICG/pending-beacon/issues?q=is%3Aissue+is%3Aopen+label%3Afetch-based-api).

0 commit comments

Comments
 (0)