mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-14 04:39:43 +08:00
123 lines
4.0 KiB
JavaScript
123 lines
4.0 KiB
JavaScript
// AbortSignal/AbortController through fetch, differentially against Node's
|
|
// undici: pre-aborted signals, mid-flight aborts, mid-stream aborts (the
|
|
// pending read rejects), AbortSignal.timeout, AbortSignal.any, custom
|
|
// abort reasons, and the no-abort quiet path — byte-exact output both
|
|
// sides, so the error SHAPES (DOMException, name, message, Error
|
|
// inheritance, legacy code) are pinned to Node's exactly.
|
|
const out = (line) => process.stdout.write(line + "\n");
|
|
const show = (tag, e) =>
|
|
out(`${tag} dom=${e instanceof DOMException} err=${e instanceof Error} code=${e.code} ${e.name}: ${e.message}`);
|
|
|
|
export function runAbort(base) {
|
|
(async () => {
|
|
// an already-aborted signal rejects before any request happens
|
|
try {
|
|
await fetch(base + "/text", { signal: AbortSignal.abort() });
|
|
out("pre no-throw");
|
|
} catch (e) {
|
|
show("pre", e);
|
|
}
|
|
// a custom abort reason is carried verbatim
|
|
try {
|
|
await fetch(base + "/text", { signal: AbortSignal.abort("my custom reason") });
|
|
out("custom no-throw");
|
|
} catch (e) {
|
|
out(`custom typeof=${typeof e} ${e}`);
|
|
}
|
|
// abort mid-flight, before any response arrives
|
|
{
|
|
const c = new AbortController();
|
|
const p = fetch(base + "/slow", { signal: c.signal });
|
|
c.abort();
|
|
try {
|
|
await p;
|
|
out("mid no-throw");
|
|
} catch (e) {
|
|
show("mid", e);
|
|
}
|
|
}
|
|
// abort mid-stream: the pending read rejects with the reason
|
|
{
|
|
const c = new AbortController();
|
|
const res = await fetch(base + "/drip", { signal: c.signal });
|
|
const reader = res.body.getReader();
|
|
const first = await reader.read();
|
|
out(`drip-first ${new TextDecoder().decode(first.value)}`);
|
|
const pending = reader.read();
|
|
c.abort();
|
|
try {
|
|
await pending;
|
|
out("stream no-throw");
|
|
} catch (e) {
|
|
show("stream", e);
|
|
}
|
|
}
|
|
// AbortSignal.timeout fires while the transfer is in flight
|
|
try {
|
|
await fetch(base + "/slow", { signal: AbortSignal.timeout(100) });
|
|
out("timeout no-throw");
|
|
} catch (e) {
|
|
show("timeout", e);
|
|
}
|
|
// AbortSignal.any follows its first-aborting member (the AI-SDK
|
|
// combine-signals shape: a user controller plus a timeout)
|
|
{
|
|
const c = new AbortController();
|
|
const combined = AbortSignal.any([c.signal, AbortSignal.timeout(4000)]);
|
|
const p = fetch(base + "/slow", { signal: combined });
|
|
c.abort();
|
|
try {
|
|
await p;
|
|
out("any no-throw");
|
|
} catch (e) {
|
|
show("any", e);
|
|
}
|
|
}
|
|
// a Request-carried signal aborts the fetch built from it
|
|
{
|
|
const c = new AbortController();
|
|
const req = new Request(base + "/slow", { signal: c.signal });
|
|
const p = fetch(req);
|
|
c.abort();
|
|
try {
|
|
await p;
|
|
out("request no-throw");
|
|
} catch (e) {
|
|
show("request", e);
|
|
}
|
|
}
|
|
// the quiet path: a signal that never aborts changes nothing, and an
|
|
// abort AFTER the body was consumed is a no-op
|
|
{
|
|
const c = new AbortController();
|
|
const res = await fetch(base + "/text", { signal: c.signal });
|
|
out(`quiet ${res.status} ${JSON.stringify(await res.text())}`);
|
|
c.abort();
|
|
out(`quiet-after aborted=${c.signal.aborted} reason=${c.signal.reason.name}`);
|
|
}
|
|
// signal surface: state, listener order, once, throwIfAborted
|
|
{
|
|
const c = new AbortController();
|
|
const s = c.signal;
|
|
out(`surface aborted=${s.aborted} reason=${s.reason}`);
|
|
const seen = [];
|
|
s.onabort = () => seen.push("onabort");
|
|
s.addEventListener("abort", () => seen.push("first"));
|
|
const twice = () => seen.push("once");
|
|
s.addEventListener("abort", twice, { once: true });
|
|
c.abort();
|
|
c.abort(); // second abort is a no-op
|
|
out(`surface fired=${seen.join(",")}`);
|
|
try {
|
|
s.throwIfAborted();
|
|
out("surface no-throw");
|
|
} catch (e) {
|
|
show("surface-throw", e);
|
|
}
|
|
}
|
|
out("done");
|
|
})().catch((e) => {
|
|
out(`FATAL ${e}`);
|
|
});
|
|
}
|