mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(ext/web): Add AbortSignal.timeout()
(#13687)
This commit is contained in:
parent
5eb0e4c2df
commit
9f494dc405
5 changed files with 126 additions and 10 deletions
|
@ -679,3 +679,77 @@ Deno.test({
|
|||
Deno.refTimer(NaN);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "AbortSignal.timeout() with no listeners",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const signal = AbortSignal.timeout(2000);
|
||||
|
||||
// This unref timer expires before the signal, and if it does expire, then
|
||||
// it means the signal has kept the event loop alive.
|
||||
const timer = setTimeout(() => console.log("Unexpected!"), 1500);
|
||||
Deno.unrefTimer(timer);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "AbortSignal.timeout() with listeners",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const signal = AbortSignal.timeout(1000);
|
||||
signal.addEventListener("abort", () => console.log("Event fired!"));
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "Event fired!\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "AbortSignal.timeout() with removed listeners",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const signal = AbortSignal.timeout(2000);
|
||||
|
||||
const callback = () => console.log("Unexpected: Event fired");
|
||||
signal.addEventListener("abort", callback);
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("Removing the listener.");
|
||||
signal.removeEventListener("abort", callback);
|
||||
}, 500);
|
||||
|
||||
Deno.unrefTimer(
|
||||
setTimeout(() => console.log("Unexpected: Unref timer"), 1500)
|
||||
);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "Removing the listener.\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "AbortSignal.timeout() with listener for a non-abort event",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const signal = AbortSignal.timeout(2000);
|
||||
|
||||
signal.addEventListener("someOtherEvent", () => {
|
||||
console.log("Unexpected: someOtherEvent called");
|
||||
});
|
||||
|
||||
Deno.unrefTimer(
|
||||
setTimeout(() => console.log("Unexpected: Unref timer"), 1500)
|
||||
);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -868,6 +868,10 @@
|
|||
return target?.[eventTargetData]?.mode ?? null;
|
||||
}
|
||||
|
||||
function listenerCount(target, type) {
|
||||
return getListeners(target)?.[type]?.length ?? 0;
|
||||
}
|
||||
|
||||
function getDefaultTargetData() {
|
||||
return {
|
||||
assignedSlot: false,
|
||||
|
@ -1326,6 +1330,7 @@
|
|||
window.__bootstrap.eventTarget = {
|
||||
EventTarget,
|
||||
setEventTargetData,
|
||||
listenerCount,
|
||||
};
|
||||
window.__bootstrap.event = {
|
||||
setIsTrusted,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
((window) => {
|
||||
const webidl = window.__bootstrap.webidl;
|
||||
const { setIsTrusted, defineEventHandler } = window.__bootstrap.event;
|
||||
const { listenerCount } = window.__bootstrap.eventTarget;
|
||||
const {
|
||||
Set,
|
||||
SetPrototypeAdd,
|
||||
|
@ -14,6 +15,7 @@
|
|||
Symbol,
|
||||
TypeError,
|
||||
} = window.__bootstrap.primordials;
|
||||
const { setTimeout, refTimer, unrefTimer } = window.__bootstrap.timers;
|
||||
|
||||
const add = Symbol("[[add]]");
|
||||
const signalAbort = Symbol("[[signalAbort]]");
|
||||
|
@ -21,6 +23,7 @@
|
|||
const abortReason = Symbol("[[abortReason]]");
|
||||
const abortAlgos = Symbol("[[abortAlgos]]");
|
||||
const signal = Symbol("[[signal]]");
|
||||
const timerId = Symbol("[[timerId]]");
|
||||
|
||||
const illegalConstructorKey = Symbol("illegalConstructorKey");
|
||||
|
||||
|
@ -34,6 +37,27 @@
|
|||
return signal;
|
||||
}
|
||||
|
||||
static timeout(millis) {
|
||||
const prefix = "Failed to call 'AbortSignal.timeout'";
|
||||
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||
millis = webidl.converters["unsigned long long"](millis, {
|
||||
enforceRange: true,
|
||||
});
|
||||
|
||||
const signal = new AbortSignal(illegalConstructorKey);
|
||||
signal[timerId] = setTimeout(
|
||||
() => {
|
||||
signal[timerId] = null;
|
||||
signal[signalAbort](
|
||||
new DOMException("Signal timed out.", "TimeoutError"),
|
||||
);
|
||||
},
|
||||
millis,
|
||||
);
|
||||
unrefTimer(signal[timerId]);
|
||||
return signal;
|
||||
}
|
||||
|
||||
[add](algorithm) {
|
||||
if (this.aborted) {
|
||||
return;
|
||||
|
@ -73,6 +97,7 @@
|
|||
super();
|
||||
this[abortReason] = undefined;
|
||||
this[abortAlgos] = null;
|
||||
this[timerId] = null;
|
||||
this[webidl.brand] = webidl.brand;
|
||||
}
|
||||
|
||||
|
@ -92,6 +117,25 @@
|
|||
throw this[abortReason];
|
||||
}
|
||||
}
|
||||
|
||||
// `addEventListener` and `removeEventListener` have to be overriden in
|
||||
// order to have the timer block the event loop while there are listeners.
|
||||
// `[add]` and `[remove]` don't ref and unref the timer because they can
|
||||
// only be used by Deno internals, which use it to essentially cancel async
|
||||
// ops which would block the event loop.
|
||||
addEventListener(...args) {
|
||||
super.addEventListener(...args);
|
||||
if (this[timerId] !== null && listenerCount(this, "abort") > 0) {
|
||||
refTimer(this[timerId]);
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListener(...args) {
|
||||
super.removeEventListener(...args);
|
||||
if (this[timerId] !== null && listenerCount(this, "abort") === 0) {
|
||||
unrefTimer(this[timerId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
defineEventHandler(AbortSignal.prototype, "abort");
|
||||
|
||||
|
|
1
ext/web/lib.deno_web.d.ts
vendored
1
ext/web/lib.deno_web.d.ts
vendored
|
@ -307,6 +307,7 @@ declare var AbortSignal: {
|
|||
prototype: AbortSignal;
|
||||
new (): AbortSignal;
|
||||
abort(reason?: any): AbortSignal;
|
||||
timeout(milliseconds: number): AbortSignal;
|
||||
};
|
||||
|
||||
interface FileReaderEventMap {
|
||||
|
|
|
@ -961,16 +961,8 @@
|
|||
},
|
||||
"dom": {
|
||||
"abort": {
|
||||
"AbortSignal.any.html": [
|
||||
"AbortSignal.timeout() returns a non-aborted signal",
|
||||
"Signal returned by AbortSignal.timeout() times out",
|
||||
"AbortSignal timeouts fire in order"
|
||||
],
|
||||
"AbortSignal.any.worker.html": [
|
||||
"AbortSignal.timeout() returns a non-aborted signal",
|
||||
"Signal returned by AbortSignal.timeout() times out",
|
||||
"AbortSignal timeouts fire in order"
|
||||
],
|
||||
"AbortSignal.any.html": true,
|
||||
"AbortSignal.any.worker.html": true,
|
||||
"event.any.html": true,
|
||||
"event.any.worker.html": true
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue