-
Notifications
You must be signed in to change notification settings - Fork 0
services track
io.track deduplicates in-flight requests at the run level: concurrent identical GETs share a single decoded envelope instead of each hitting the network and reparsing the body.
import io from 'double-meh';
const [a, b] = await Promise.all([
io.get('https://example.com/dup'),
io.get('https://example.com/dup')
]);
// one network call; a and b are the same decoded resultThe first caller of a key marks it in-flight and runs the request; later callers awaiting the same key receive the same resolved envelope — decoded once, not cloned and reparsed. Dedup is keyed by method + URL + representation (the effective Accept — see keys & URLs), so concurrent GETs asking for different representations each get their own request rather than sharing one body.
const [a, b] = await Promise.all([
io.full.get('https://example.com/shared'),
io.full.get('https://example.com/shared')
]);
a === b; // true — one envelope object, decoded at the run levelTracking is GET-only, hard: track: true on a non-GET does nothing. Within GETs, track: false opts a request out, and io.track.theDefault — a boolean or a predicate (options) => boolean, default options => !options.transport — decides for requests that don't say. So GETs on a custom transport are not tracked unless they pass track: true.
The shared request runs on the tracker's own signal, not any caller's. Aborting your signal detaches only you — your promise rejects with your AbortError (or TimedOut), while other waiters keep the request alive and get the response. The wire request aborts only when the last waiter leaves; a caller without a signal pins it to completion.
const mine = new AbortController();
const a = io.get('/report', null, {signal: mine.signal});
const b = io.get('/report'); // deduped onto the same request
mine.abort(); // a rejects; b still resolves with the responsestream: true requests are never deduped — each streaming caller gets its own response and body. A request with a decode override is not deduped either: the shared envelope is decoded once, with the leader's settings, so a custom decode must stay private. (The cache is unaffected — it stores bytes, and every reader decodes its own copy.)
await Promise.all([
io.full.get('https://example.com/s', null, {stream: true}),
io.full.get('https://example.com/s', null, {stream: true})
]); // two independent responsestrack: 'wait' registers interest in a key without firing a request. The returned promise stays pending until a later real request (or io.adopt) for the same key lands, then resolves every waiter with that result. track: 'wait' on a non-trackable request (a non-GET, or a stream) throws a TypeError.
const waiter = io.get({url: 'https://example.com/w', track: 'wait'});
// nothing has been sent yet
const real = io.get('https://example.com/w'); // the real request fires
const [w, r] = await Promise.all([waiter, real]); // both resolve to the landed dataThese expose the in-flight registry directly:
-
io.track.fly(target)— get (creating if needed) the deferred for a target. -
io.track.flyByKey(key)— same, addressed by a raw key fromio.makeKey. -
io.track.isFlying(target)— the deferred if the key is currently in flight, elseundefined.
io.track.fly('https://example.com/soon'); // register interest by target
const pending = io.track.isFlying('https://example.com/soon');
if (pending) await pending.promise;io.adopt(target, source) fulfills a key from an externally issued response (a Promise<Response> or Response) instead of the network — it marks the key flying so a later real request does not also fire, resolves all waiters, and (when the cache is active and the request is cacheable) saves the GET. This is the basis of the code-forward handoff: a page can pre-register in-flight keys via fly and later feed their responses via adopt, so client hydration reuses server-issued data.
io.adopt('https://example.com/me', json({from: 'prelude'}));
const data = await io.get('https://example.com/me'); // no network — uses the adopted responseThe main entry attaches tracking for you. io.track.attach() / io.track.detach() toggle io.track.active and return io.
io.track.detach(); // disable dedup
io.track.attach(); // re-enableGuides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook