Expand Node 24 promise timer compatibility (#637)

This commit is contained in:
Chris Tate
2026-10-03 13:00:14 -05:00
committed by GitHub
parent b76aa555f0
commit 64a66ddd46
17 changed files with 645 additions and 218 deletions
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "11c3ebd9479ef9f7eca0",
"artifactVersion": "382e8ff3544e0a3e29a8",
"rowCount": 3662
}
+43 -43
View File
@@ -1067,9 +1067,9 @@
},
"dynamic": {
"supported": 0,
"partial": 10,
"partial": 22,
"refused": 0,
"not-implemented": 15,
"not-implemented": 3,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -44567,7 +44567,7 @@
},
"dynamic": {
"status": "partial",
"detail": "Provided when the embedded npm module bootstrap runs; absent from standalone dynamic islands.",
"detail": "Provided by the embedded engine's web/global compatibility layer.",
"verification": "test-backed"
}
},
@@ -44707,7 +44707,7 @@
},
"dynamic": {
"status": "partial",
"detail": "Provided when the embedded npm module bootstrap runs; absent from standalone dynamic islands.",
"detail": "Provided by the embedded engine's web/global compatibility layer.",
"verification": "test-backed"
}
},
@@ -78240,7 +78240,7 @@
},
"dynamic": {
"status": "partial",
"detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.",
"detail": "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.",
"verification": "test-backed"
}
},
@@ -78268,7 +78268,7 @@
},
"dynamic": {
"status": "partial",
"detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.",
"detail": "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.",
"verification": "test-backed"
}
},
@@ -78296,7 +78296,7 @@
},
"dynamic": {
"status": "partial",
"detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.",
"detail": "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.",
"verification": "test-backed"
}
},
@@ -78323,9 +78323,9 @@
"verification": "registry-gap"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.",
"verification": "test-backed"
}
},
{
@@ -78351,9 +78351,9 @@
"verification": "registry-gap"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.",
"verification": "test-backed"
}
},
{
@@ -78407,9 +78407,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78435,9 +78435,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78463,9 +78463,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78491,9 +78491,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78547,9 +78547,9 @@
"verification": "registry-gap"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78575,9 +78575,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78603,9 +78603,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78631,9 +78631,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78659,9 +78659,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
@@ -78715,9 +78715,9 @@
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
"detail": "This API is not implemented by the dynamic-island shim.",
"verification": "registry-gap"
"status": "partial",
"detail": "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.",
"verification": "test-backed"
}
},
{
+4 -2
View File
@@ -19,6 +19,8 @@
"wasi": "by-design"
},
"features": [
{ "chapter": "timers", "symbols": ["timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "timersPromises.scheduler.wait", "timersPromises.scheduler.yield"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-promise-timers/main.ts", "tests/fixtures/npm/cases/node24-timers-unref/main.ts"] },
{ "chapter": "timers", "symbols": ["immediate.ref", "immediate.unref", "immediate.hasRef", "timeout.ref", "timeout.unref", "timeout.hasRef", "timeout.refresh", "timeout.close"], "signatures": ["`immediate[Symbol.dispose]()`", "`timeout[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/island-timers/main.ts", "tests/fixtures/npm/cases/node24-promise-timers/main.ts", "tests/fixtures/npm/cases/node24-timers-unref/main.ts"] },
{ "chapter": "events", "symbols": ["events.once", "events.on", "events.addAbortListener"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-async-events/main.ts"] },
{ "chapter": "events", "symbols": ["emitter.on", "emitter.addListener", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.removeListener", "emitter.off", "emitter.removeAllListeners", "emitter.emit", "emitter.listenerCount", "emitter.eventNames", "events.errorMonitor"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts", "tests/fixtures/npm/cases/node24-event-emitter/main.ts"] },
{ "chapter": "util", "symbols": ["util.promisify", "util.callbackify"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts", "tests/fixtures/npm/cases/node24-util-adapters/main.ts"] },
@@ -91,8 +93,8 @@
"util/types": { "exports": ["isAnyArrayBuffer", "isArrayBufferView", "isArgumentsObject", "isArrayBuffer", "isAsyncFunction", "isBigInt64Array", "isBigUint64Array", "isBooleanObject", "isBoxedPrimitive", "isBigIntObject", "isCryptoKey", "isDataView", "isDate", "isExternal", "isFloat16Array", "isFloat32Array", "isFloat64Array", "isGeneratorFunction", "isGeneratorObject", "isInt8Array", "isInt16Array", "isInt32Array", "isKeyObject", "isMap", "isMapIterator", "isModuleNamespaceObject", "isNativeError", "isNumberObject", "isPromise", "isProxy", "isRegExp", "isSet", "isSetIterator", "isSharedArrayBuffer", "isStringObject", "isSymbolObject", "isTypedArray", "isUint8Array", "isUint8ClampedArray", "isUint16Array", "isUint32Array", "isWeakMap", "isWeakSet"], "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts"] }
},
"globals": {
"supported": ["atob", "btoa", "clearInterval", "clearTimeout", "queueMicrotask", "setInterval", "setTimeout", "structuredClone", "AbortController", "AbortSignal", "Blob", "CustomEvent", "DOMException", "Event", "EventTarget", "File", "Headers", "MessageChannel", "MessageEvent", "MessagePort", "Request", "Response", "TextDecoder", "TextDecoderStream", "TextEncoder", "TransformStream", "URLSearchParams", "ReadableStream"],
"npmSupported": ["Buffer", "URL", "clearImmediate", "setImmediate"],
"supported": ["atob", "btoa", "clearImmediate", "clearInterval", "clearTimeout", "queueMicrotask", "setImmediate", "setInterval", "setTimeout", "structuredClone", "AbortController", "AbortSignal", "Blob", "CustomEvent", "DOMException", "Event", "EventTarget", "File", "Headers", "MessageChannel", "MessageEvent", "MessagePort", "Request", "Response", "TextDecoder", "TextDecoderStream", "TextEncoder", "TransformStream", "URLSearchParams", "ReadableStream"],
"npmSupported": ["Buffer", "URL"],
"absent": ["BroadcastChannel", "ByteLengthQueuingStrategy", "CloseEvent", "CompressionStream", "CountQueuingStrategy", "CryptoKey", "DecompressionStream", "EventSource", "FormData", "Navigator", "ReadableByteStreamController", "ReadableStreamBYOBReader", "ReadableStreamBYOBRequest", "Storage", "SubtleCrypto", "TextEncoderStream", "TransformStreamDefaultController", "URLPattern", "WebAssembly", "WebSocket", "WritableStream", "WritableStreamDefaultController", "WritableStreamDefaultWriter"],
"evidence": ["tests/harness/web-globals.test.ts"]
}
@@ -14,8 +14,8 @@
"implement": 81
},
"dynamic": {
"audit-partial": 816,
"verify-gap": 2222,
"audit-partial": 828,
"verify-gap": 2210,
"replace-refusal": 112,
"implement": 218
}
@@ -45956,7 +45956,7 @@
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-npm-global:clearImmediate",
"source": "island-global:clearImmediate",
"tests": [
"tests/harness/web-globals.test.ts"
]
@@ -46144,7 +46144,7 @@
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-npm-global:setImmediate",
"source": "island-global:setImmediate",
"tests": [
"tests/harness/web-globals.test.ts"
]
@@ -86829,9 +86829,10 @@
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-export:node:timers/promises.setTimeout",
"source": "island-feature:timers.timersPromises.setTimeout",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
@@ -86869,9 +86870,10 @@
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-export:node:timers/promises.setImmediate",
"source": "island-feature:timers.timersPromises.setImmediate",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
@@ -86909,9 +86911,10 @@
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-export:node:timers/promises.setInterval",
"source": "island-feature:timers.timersPromises.setInterval",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
@@ -86940,12 +86943,15 @@
"tests": []
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timersPromises.scheduler.wait",
"tests": [
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -86973,12 +86979,15 @@
"tests": []
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timersPromises.scheduler.yield",
"tests": [
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87044,12 +87053,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.immediate.hasRef",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87082,12 +87095,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.immediate.ref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87120,12 +87137,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.immediate.unref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87155,12 +87176,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.immediate",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87221,12 +87246,16 @@
"tests": []
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timeout.close",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87259,12 +87288,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timeout.hasRef",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87297,12 +87330,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timeout.ref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87335,12 +87372,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timeout.refresh",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87373,12 +87414,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timeout.unref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -87445,12 +87490,16 @@
]
},
"dynamic": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "island-unmatched:timers",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "island-feature:timers.timeout",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
}
}
},
@@ -1188,9 +1188,9 @@
},
"dynamic": {
"supported": 0,
"partial": 10,
"partial": 22,
"refused": 0,
"not-implemented": 15,
"not-implemented": 3,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -61878,7 +61878,7 @@
},
"dynamic": {
"status": "partial",
"evidence": "island-npm-global:clearImmediate",
"evidence": "island-global:clearImmediate",
"tests": [
"tests/harness/web-globals.test.ts"
]
@@ -62043,7 +62043,7 @@
},
"dynamic": {
"status": "partial",
"evidence": "island-npm-global:setImmediate",
"evidence": "island-global:setImmediate",
"tests": [
"tests/harness/web-globals.test.ts"
]
@@ -104044,7 +104044,10 @@
"status": "partial",
"evidence": "derived:descendants",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/misc-shims/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "chapter"
@@ -104375,9 +104378,10 @@
},
"dynamic": {
"status": "partial",
"evidence": "island-export:node:timers/promises.setTimeout",
"evidence": "island-feature:timers.timersPromises.setTimeout",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
@@ -104411,9 +104415,10 @@
},
"dynamic": {
"status": "partial",
"evidence": "island-export:node:timers/promises.setImmediate",
"evidence": "island-feature:timers.timersPromises.setImmediate",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
@@ -104447,9 +104452,10 @@
},
"dynamic": {
"status": "partial",
"evidence": "island-export:node:timers/promises.setInterval",
"evidence": "island-feature:timers.timersPromises.setInterval",
"tests": [
"tests/fixtures/npm/cases/misc-shims/main.ts"
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
@@ -104476,8 +104482,12 @@
"evidence": "compiler-unmatched:timers"
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timersPromises.scheduler.wait",
"tests": [
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104503,8 +104513,12 @@
"evidence": "compiler-unmatched:timers"
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timersPromises.scheduler.yield",
"tests": [
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104563,8 +104577,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.immediate.hasRef",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104596,8 +104615,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.immediate.ref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104629,8 +104653,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.immediate.unref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104659,8 +104688,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.immediate",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104713,8 +104747,13 @@
"evidence": "compiler-unmatched:timers"
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timeout.close",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104746,8 +104785,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timeout.hasRef",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104779,8 +104823,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timeout.ref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104812,8 +104861,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timeout.refresh",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104845,8 +104899,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timeout.unref",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
@@ -104907,8 +104966,13 @@
]
},
"dynamic": {
"status": "not-implemented",
"evidence": "island-unmatched:timers"
"status": "partial",
"evidence": "island-feature:timers.timeout",
"tests": [
"tests/fixtures/npm/cases/island-timers/main.ts",
"tests/fixtures/npm/cases/node24-promise-timers/main.ts",
"tests/fixtures/npm/cases/node24-timers-unref/main.ts"
]
},
"anchorSource": "exact"
},
+2
View File
@@ -678,6 +678,8 @@ function publicDetail(tier) {
return "No dynamic-island implementation exists for this Node API family yet.";
}
if (source.startsWith("island-feature:")) {
if (source.startsWith("island-feature:timers.timersPromises.")) return "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.";
if (source.startsWith("island-feature:timers.")) return "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.";
if (source === "island-feature:events.events.once") return "Waits for EventEmitter or EventTarget events with AbortSignal cancellation, Node abort errors, and listener cleanup.";
if (source === "island-feature:events.events.on") return "Iterates EventEmitter or EventTarget events with buffered delivery, close events, cancellation, watermark backpressure, and listener cleanup.";
if (source === "island-feature:events.events.addAbortListener") return "Registers disposable abort listeners that run even when ordinary propagation is stopped; already-aborted signals schedule the callback in a microtask.";
@@ -13426,6 +13426,18 @@
"<repo>/tests/fixtures/npm/cases/node24-async-events/main.ts"
],
"diags": []
},
"<repo>/tests/fixtures/npm/cases/node24-promise-timers/main.ts": {
"order": [
"<repo>/tests/fixtures/npm/cases/node24-promise-timers/main.ts"
],
"diags": []
},
"<repo>/tests/fixtures/npm/cases/node24-timers-unref/main.ts": {
"order": [
"<repo>/tests/fixtures/npm/cases/node24-timers-unref/main.ts"
],
"diags": []
}
}
}
+89 -32
View File
@@ -9459,9 +9459,8 @@ static const char isl_modules_bootstrap[] =
" c.default = c;\n"
" return c;\n"
" });\n"
/* node:timers (+ timers/promises) over the island's timer
* bridge; setImmediate rides a zero-delay timer (Node's check
* phase does not exist here — documented divergence). */
/* node:timers and timers/promises share the native timer heap and
* immediate queue through the web prelude's handle objects. */
" builtins.timers = memo(() => {\n"
" const t = {\n"
" setTimeout: globalThis.setTimeout,\n"
@@ -9475,30 +9474,94 @@ static const char isl_modules_bootstrap[] =
" return t;\n"
" });\n"
" builtins['timers/promises'] = memo(() => {\n"
" const delay = (ms, value, options) => new Promise((resolve, reject) => {\n"
" const t = globalThis.setTimeout(() => resolve(value), ms);\n"
" if (options !== undefined && options !== null && options.signal !== undefined && typeof options.signal.addEventListener === 'function') {\n"
" options.signal.addEventListener('abort', () => {\n"
" globalThis.clearTimeout(t);\n"
" const e = new Error('The operation was aborted');\n"
" e.name = 'AbortError';\n"
" e.code = 'ABORT_ERR';\n"
" reject(e);\n"
" });\n"
" const resistant = Symbol.for('scriptc.events.resistStopPropagation');\n"
" const checkDelay = (delay) => {\n"
" if (delay !== undefined && typeof delay !== 'number') envArgumentError('delay', 'of type number', delay);\n"
" };\n"
" const checkOptions = (options) => {\n"
" if (options === null || typeof options !== 'object' || Array.isArray(options)) envArgumentError('options', 'of type object', options);\n"
" if (options.signal !== undefined) {\n"
" const signal = options.signal;\n"
" if (signal === null || typeof signal !== 'object' || !('aborted' in signal)) envArgumentError('options.signal', 'an instance of AbortSignal', signal);\n"
" }\n"
" });\n"
" if (options.ref !== undefined) {\n"
" const ref = options.ref;\n"
" if (typeof ref !== 'boolean') envArgumentError('options.ref', 'of type boolean', ref);\n"
" }\n"
" const { signal, ref = true } = options;\n"
" return { signal, ref };\n"
" };\n"
" const abortError = (signal) => {\n"
" const error = new Error('The operation was aborted', { cause: signal.reason });\n"
" error.name = 'AbortError'; error.code = 'ABORT_ERR';\n"
" return error;\n"
" };\n"
" const wait = (immediate, delay, value, options) => {\n"
" let signal, ref;\n"
" try {\n"
" if (!immediate) checkDelay(delay);\n"
" ({ signal, ref } = checkOptions(options));\n"
" } catch (error) { return Promise.reject(error); }\n"
" if (signal && signal.aborted) return Promise.reject(abortError(signal));\n"
" let timer, onCancel;\n"
" const promise = new Promise((resolve, reject) => {\n"
" timer = immediate ? globalThis.setImmediate(resolve, value) : globalThis.setTimeout(resolve, delay, value);\n"
" if (!ref) timer.unref();\n"
" if (signal) {\n"
" onCancel = () => {\n"
" if (!timer._destroyed) {\n"
" if (immediate) globalThis.clearImmediate(timer); else globalThis.clearTimeout(timer);\n"
" reject(abortError(signal));\n"
" }\n"
" };\n"
" signal.addEventListener('abort', onCancel, { __proto__: null, [resistant]: true });\n"
" }\n"
" });\n"
" return onCancel ? promise.finally(() => signal.removeEventListener('abort', onCancel)) : promise;\n"
" };\n"
" function setTimeout(delay, value, options = {}) { return wait(false, delay, value, options); }\n"
" function setImmediate(value, options = {}) { return wait(true, undefined, value, options); }\n"
" async function* setInterval(delay, value, options = {}) {\n"
" checkDelay(delay);\n"
" const { signal, ref } = checkOptions(options);\n"
" if (signal && signal.aborted) throw abortError(signal);\n"
" let timer, onCancel;\n"
" try {\n"
" let notYielded = 0, callback;\n"
" timer = globalThis.setInterval(() => {\n"
" notYielded++;\n"
" if (callback) { callback(); callback = undefined; }\n"
" }, delay);\n"
" if (!ref) timer.unref();\n"
" if (signal) {\n"
" onCancel = () => {\n"
" globalThis.clearInterval(timer);\n"
" if (callback) { callback(Promise.reject(abortError(signal))); callback = undefined; }\n"
" };\n"
" signal.addEventListener('abort', onCancel, { __proto__: null, once: true, [resistant]: true });\n"
" }\n"
" while (!(signal && signal.aborted)) {\n"
" if (notYielded === 0) await new Promise(resolve => callback = resolve);\n"
" for (; notYielded > 0; notYielded--) yield value;\n"
" }\n"
" throw abortError(signal);\n"
" } finally {\n"
" globalThis.clearInterval(timer);\n"
" if (signal) signal.removeEventListener('abort', onCancel);\n"
" }\n"
" }\n"
" const schedulerBrand = Symbol('kScheduler');\n"
" const checkScheduler = (receiver) => {\n"
" if (!receiver[schedulerBrand]) { const error = new TypeError('Value of \"this\" must be of type Scheduler'); error.code = 'ERR_INVALID_THIS'; throw error; }\n"
" };\n"
" class Scheduler {\n"
" constructor() { const error = new TypeError('Illegal constructor'); error.code = 'ERR_ILLEGAL_CONSTRUCTOR'; throw error; }\n"
" yield() { checkScheduler(this); return setImmediate(); }\n"
" wait(delay, options) { checkScheduler(this); return setTimeout(delay, undefined, options); }\n"
" }\n"
" const scheduler = Object.create(Scheduler.prototype); scheduler[schedulerBrand] = true;\n"
" const tp = {\n"
" setTimeout: delay,\n"
" setImmediate: (value) => delay(0, value),\n"
" setInterval: (ms, value, options) => ({\n"
" async *[Symbol.asyncIterator]() {\n"
" for (;;) {\n"
" await delay(ms, undefined, options);\n"
" yield value;\n"
" }\n"
" },\n"
" }),\n"
" scheduler: { wait: (ms) => delay(ms), yield: () => delay(0) },\n"
" setTimeout, setImmediate, setInterval, scheduler,\n"
" };\n"
" tp.default = tp;\n"
" return tp;\n"
@@ -10050,15 +10113,9 @@ static const char isl_modules_bootstrap[] =
/* Node's Buffer global (and `global` itself) — embedded CJS
* reaches both without requiring anything. */
" globalThis.Buffer = builtins.buffer().Buffer;\n"
/* setImmediate/clearImmediate globals (zero-delay timers — no
* check phase in the island; documented divergence), and the
* global console upgraded to Node's util.format semantics for
/* The global console upgraded to Node's util.format semantics for
* npm builds (the web prelude's String() console stays for
* non-npm islands). */
" if (globalThis.setImmediate === undefined) {\n"
" globalThis.setImmediate = (fn, ...args) => globalThis.setTimeout(fn, 0, ...args);\n"
" globalThis.clearImmediate = (t) => globalThis.clearTimeout(t);\n"
" }\n"
" {\n"
" const fmt = (...a) => builtins.util().formatWithOptions({}, ...a);\n"
" const to = (fd, prefix) => (...a) => { host.write(fd, (prefix || '') + fmt(...a) + '\\n'); };\n"
+83 -20
View File
@@ -62,43 +62,77 @@ static const char web_prelude[] =
" * (host.setTimer/host.clearTimer) — REF'd like Node's (an armed\n"
" * timer keeps the process alive), FIFO-ordered against static\n"
" * timers on one heap, Node's <1ms clamp. Returns a Timeout-shaped\n"
" * object (ref/unref/refresh/close, numeric via toPrimitive) that\n"
" * clearTimeout/clearInterval accept alongside plain ids; unref is\n"
" * accepted but not honored (the entry stays ref'd — a documented\n"
" * approximation). */\n"
" * object with real ref/unref/refresh/close operations. Immediates\n"
" * use the native check queue, independently of timer ids. */\n"
" const timers = new Map();\n"
" class Timeout {\n"
" constructor(fn, delay, repeat) {\n"
" constructor(fn, delay, args, repeat) {\n"
" this._fn = fn;\n"
" this._delay = delay;\n"
" this._args = args;\n"
" this._repeat = repeat;\n"
" this._id = host.setTimer(fn, delay, repeat);\n"
" this._reffed = true;\n"
" this._closed = false;\n"
" this._callback = () => {\n"
" if (!this._repeat) { this._destroyed = true; timers.delete(this._id); }\n"
" this._fn.apply(this, this._args);\n"
" };\n"
" this._arm();\n"
" }\n"
" ref() { return this; }\n"
" unref() { return this; }\n"
" hasRef() { return true; }\n"
" _arm() {\n"
" this._destroyed = false;\n"
" this._id = host.setTimer(this._callback, this._delay, this._repeat, false);\n"
" timers.set(this._id, this);\n"
" if (!this._reffed) host.refTimer(this._id, false, false);\n"
" }\n"
" ref() { this._reffed = true; host.refTimer(this._id, true, false); return this; }\n"
" unref() { this._reffed = false; host.refTimer(this._id, false, false); return this; }\n"
" hasRef() { return this._reffed; }\n"
" refresh() {\n"
" host.clearTimer(this._id);\n"
" this._id = host.setTimer(this._fn, this._delay, this._repeat);\n"
" if (this._closed) return this;\n"
" if (this._destroyed) this._arm();\n"
" else host.refreshTimer(this._id);\n"
" return this;\n"
" }\n"
" close() { host.clearTimer(this._id); return this; }\n"
" close() { this._closed = true; this._destroyed = true; timers.delete(this._id); host.clearTimer(this._id, false); return this; }\n"
" [Symbol.dispose]() { this.close(); }\n"
" [Symbol.toPrimitive]() { return this._id; }\n"
" }\n"
" const mkTimer = (fn, ms, args, repeat) => {\n"
" if (typeof fn !== 'function') {\n"
" throw new TypeError('The \"callback\" argument must be of type function. Received type ' + typeof fn);\n"
" }\n"
" const cb = args.length === 0 ? fn : () => fn(...args);\n"
" return new Timeout(cb, Number(ms), repeat);\n"
" return new Timeout(fn, Number(ms), args, repeat);\n"
" };\n"
" g.setTimeout = (fn, ms, ...args) => mkTimer(fn, ms, args, false);\n"
" g.setInterval = (fn, ms, ...args) => mkTimer(fn, ms, args, true);\n"
" g.clearTimeout = (t) => {\n"
" if (t === undefined || t === null) return;\n"
" const id = typeof t === 'number' ? t : Number(t);\n"
" if (Number.isFinite(id)) host.clearTimer(id);\n"
" if (Number.isFinite(id)) {\n"
" const timer = timers.get(id);\n"
" if (timer) timer.close(); else host.clearTimer(id, false);\n"
" }\n"
" };\n"
" g.clearInterval = g.clearTimeout;\n"
" class Immediate {\n"
" constructor(fn, args) {\n"
" this._reffed = true; this._destroyed = false;\n"
" this._id = host.setTimer(() => {\n"
" this._destroyed = true; this._reffed = false; fn.apply(this, args);\n"
" }, 0, false, true);\n"
" }\n"
" ref() { if (!this._destroyed) { this._reffed = true; host.refTimer(this._id, true, true); } return this; }\n"
" unref() { if (!this._destroyed) { this._reffed = false; host.refTimer(this._id, false, true); } return this; }\n"
" hasRef() { return this._reffed; }\n"
" _close() { this._destroyed = true; this._reffed = false; host.clearTimer(this._id, true); }\n"
" [Symbol.dispose]() { this._close(); }\n"
" }\n"
" g.setImmediate = (fn, ...args) => {\n"
" if (typeof fn !== 'function') throw new TypeError('The \"callback\" argument must be of type function. Received type ' + typeof fn);\n"
" return new Immediate(fn, args);\n"
" };\n"
" g.clearImmediate = (immediate) => { if (immediate instanceof Immediate) immediate._close(); };\n"
"\n"
" class ReadableStream {\n"
" constructor(source, _strategy) {\n"
@@ -1673,7 +1707,6 @@ static void web_timer_fire_cb(ScrClosure *env) {
static JSValue web_host_set_timer(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
(void)this_val;
(void)argc;
double ms = 0;
if (JS_ToFloat64(ctx, &ms, argv[1])) return JS_EXCEPTION;
bool repeat = JS_ToBool(ctx, argv[2]) > 0;
@@ -1689,17 +1722,45 @@ static JSValue web_host_set_timer(JSContext *ctx, JSValueConst this_val,
scr_box_set_ref(box, h); /* the box owns the +1 */
ScrClosure *cb = scr_closure_new((void *)web_timer_fire_cb, 1);
cb->caps[0] = box;
double id = repeat ? scr_set_interval(cb, ms) : scr_set_timeout_handle(cb, ms);
bool immediate = argc > 3 && JS_ToBool(ctx, argv[3]) > 0;
double id = immediate ? scr_set_immediate(cb)
: repeat ? scr_set_interval(cb, ms) : scr_set_timeout_handle(cb, ms);
return JS_NewFloat64(ctx, id);
}
static JSValue web_host_clear_timer(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
(void)this_val;
double id = 0;
if (JS_ToFloat64(ctx, &id, argv[0])) return JS_EXCEPTION;
if (argc > 1 && JS_ToBool(ctx, argv[1]) > 0) scr_clear_immediate(id);
else scr_clear_interval(id);
return JS_UNDEFINED;
}
static JSValue web_host_ref_timer(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
(void)this_val;
(void)argc;
double id = 0;
if (JS_ToFloat64(ctx, &id, argv[0])) return JS_EXCEPTION;
scr_clear_interval(id);
bool reffed = JS_ToBool(ctx, argv[1]) > 0;
bool immediate = JS_ToBool(ctx, argv[2]) > 0;
if (immediate) {
if (reffed) scr_immediate_ref(id); else scr_immediate_unref(id);
} else {
if (reffed) scr_timer_ref(id); else scr_timer_unref(id);
}
return JS_UNDEFINED;
}
static JSValue web_host_refresh_timer(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
(void)this_val;
(void)argc;
double id = 0;
if (JS_ToFloat64(ctx, &id, argv[0])) return JS_EXCEPTION;
scr_timer_refresh(id);
return JS_UNDEFINED;
}
@@ -1877,8 +1938,10 @@ void scr_island_web_boot(void *jsctx) {
JS_SetPropertyStr(ctx, host, "write", JS_NewCFunction(ctx, web_host_write, "write", 2));
JS_SetPropertyStr(ctx, host, "tzname", JS_NewCFunction(ctx, web_host_tzname, "tzname", 1));
JS_SetPropertyStr(ctx, host, "timer", JS_NewCFunction(ctx, web_host_timer, "timer", 2));
JS_SetPropertyStr(ctx, host, "setTimer", JS_NewCFunction(ctx, web_host_set_timer, "setTimer", 3));
JS_SetPropertyStr(ctx, host, "clearTimer", JS_NewCFunction(ctx, web_host_clear_timer, "clearTimer", 1));
JS_SetPropertyStr(ctx, host, "setTimer", JS_NewCFunction(ctx, web_host_set_timer, "setTimer", 4));
JS_SetPropertyStr(ctx, host, "clearTimer", JS_NewCFunction(ctx, web_host_clear_timer, "clearTimer", 2));
JS_SetPropertyStr(ctx, host, "refTimer", JS_NewCFunction(ctx, web_host_ref_timer, "refTimer", 3));
JS_SetPropertyStr(ctx, host, "refreshTimer", JS_NewCFunction(ctx, web_host_refresh_timer, "refreshTimer", 1));
JSValue r = JS_Call(ctx, fn, JS_UNDEFINED, 1, (JSValueConst *)&host);
JS_FreeValue(ctx, host);
JS_FreeValue(ctx, fn);
@@ -0,0 +1,4 @@
// @dynamic
// @stderr
import { report } from "node24-promise-timers-fixture";
await report();
@@ -0,0 +1 @@
{"type":"module"}
+4
View File
@@ -0,0 +1,4 @@
// @dynamic
// @stderr
import { unref } from "node24-promise-timers-fixture";
unref();
@@ -0,0 +1 @@
{"type":"module"}
@@ -0,0 +1,2 @@
export function report(): Promise<void>;
export function unref(): void;
+165
View File
@@ -0,0 +1,165 @@
import { setTimeout as delay, setImmediate as immediate, setInterval as interval, scheduler } from 'node:timers/promises';
import timers from 'node:timers';
export async function report() {
const line = (name, value) => console.log(name, JSON.stringify(value));
const shape = e => [e.name, e.code, e.message];
const reject = async (name, fn) => { try { await fn(); line(name, 'no error'); } catch (e) { line(name, shape(e)); } };
for (const value of [null, false, '5', {}, [], 1n, Symbol('delay')]) {
await reject('timeout delay', () => delay(value));
const iterator = interval(value);
line('interval lazy validation', typeof iterator.next);
await reject('interval delay', () => iterator.next());
await reject('scheduler delay', () => scheduler.wait(value));
}
for (const value of [null, false, 3, 'bad', [], () => {}]) {
await reject('timeout options', () => delay(1, undefined, value));
await reject('immediate options', () => immediate(undefined, value));
await reject('interval options', () => interval(1, undefined, value).next());
}
for (const key of ['signal', 'ref']) {
for (const value of [null, 0, 'bad', {}]) {
const options = { [key]: value };
await reject('timeout ' + key, () => delay(1, undefined, options));
await reject('immediate ' + key, () => immediate(undefined, options));
await reject('interval ' + key, () => interval(1, undefined, options).next());
}
}
{
let threw = false, promise;
try { promise = delay('bad'); } catch { threw = true; }
line('invalid returns rejection', [threw, promise instanceof Promise]);
await promise.catch(() => {});
line('default values', [await delay(), await immediate(), await scheduler.wait(0), await scheduler.yield()]);
const value = { answer: 42 };
line('timeout value identity', await delay(1.5, value) === value);
line('immediate value identity', await immediate(value) === value);
}
for (const kind of ['timeout', 'immediate', 'scheduler']) {
for (const preaborted of [false, true]) {
const ac = new AbortController(), cause = { why: 'cancelled' };
ac.signal.addEventListener('abort', e => e.stopImmediatePropagation());
if (preaborted) ac.abort(cause);
const promise = kind === 'timeout' ? delay(60000, 'unused', { signal: ac.signal })
: kind === 'immediate' ? immediate('unused', { signal: ac.signal })
: scheduler.wait(60000, { signal: ac.signal });
if (!preaborted) ac.abort(cause);
try { await promise; } catch (e) { line('cancelled ' + kind, [preaborted, ...shape(e), e.cause === cause]); }
}
}
{
const ac = new AbortController(); ac.abort();
try { await delay(60000, undefined, { signal: ac.signal }); }
catch (e) { line('default abort cause', [shape(e), e.cause === ac.signal.reason, e.cause.name]); }
}
{
const ac = new AbortController(); let removed = 0;
const original = ac.signal.removeEventListener.bind(ac.signal);
ac.signal.removeEventListener = (...args) => { removed++; return original(...args); };
await delay(1, undefined, { signal: ac.signal });
await immediate(undefined, { signal: ac.signal });
await scheduler.wait(1, { signal: ac.signal });
const iterator = interval(1, undefined, { signal: ac.signal }); await iterator.next(); await iterator.return();
line('completed signal cleanup', removed); ac.abort('after completion');
}
{
const ac = new AbortController(); let removed = 0;
const original = ac.signal.removeEventListener.bind(ac.signal);
ac.signal.removeEventListener = (...args) => { removed++; return original(...args); };
const promise = delay(60000, undefined, { signal: ac.signal }); ac.abort('early');
await promise.catch(() => {}); line('cancelled signal cleanup', removed);
}
{
const value = { tick: 'value' }, iterator = interval(1, value);
line('interval self', iterator[Symbol.asyncIterator]() === iterator);
line('interval identity', (await iterator.next()).value === value);
line('interval return', await iterator.return('finished'));
line('interval done', await iterator.next());
let ticks = 0; for await (const value of interval(1, 'tick')) { if (value === 'tick') ticks++; if (ticks === 3) break; }
line('interval loop break', ticks);
}
for (const preaborted of [false, true]) {
const ac = new AbortController(), cause = { why: 'interval cancellation' };
ac.signal.addEventListener('abort', e => e.stopImmediatePropagation());
if (preaborted) ac.abort(cause);
const iterator = interval(60000, 'unused', { signal: ac.signal });
const a = iterator.next().catch(e => [shape(e), e.cause === cause]), b = iterator.next();
if (!preaborted) ac.abort(cause);
line('interval cancelled', [preaborted, await a, await b, await iterator.next()]);
}
{
const ac = new AbortController(), iterator = interval(5, 'buffered', { signal: ac.signal });
await iterator.next(); await delay(100); ac.abort('buffered abort');
line('interval drains buffered tick', await iterator.next()); await iterator.return();
}
{
const iterator = interval(1, 'value'), failure = new Error('thrown');
await iterator.next(); try { await iterator.throw(failure); } catch (e) { line('interval throw identity', e === failure); }
line('interval throw done', await iterator.next());
const lazy = interval('invalid delay'); line('return before start', await lazy.return('skipped'));
}
{
const keep = setTimeout(() => {}, 60000);
line('unref timeout with live loop', await delay(1, 'value', { ref: false }));
line('unref immediate with live loop', await immediate('value', { ref: false }));
const iterator = interval(1, 'value', { ref: false }); line('unref interval with live loop', await iterator.next()); await iterator.return();
await scheduler.wait(1, { ref: false }); line('unref scheduler with live loop', true); clearTimeout(keep);
}
{
const log = [];
await new Promise(resolve => setImmediate(() => {
setImmediate(() => log.push('first'));
queueMicrotask(() => log.push('microtask'));
setImmediate(() => { log.push('second'); setImmediate(() => { log.push('nested'); resolve(); }); });
}));
line('immediate queue', log);
line('global module identity', [timers.setImmediate === setImmediate, timers.clearImmediate === clearImmediate]);
}
{
const timer = setTimeout(() => {}, 60000), id = +timer;
line('timeout ref control', [timer.hasRef(), timer.unref() === timer, timer.hasRef(), timer.ref() === timer, timer.hasRef()]);
timer.unref(); timer.refresh(); line('refresh preserves ref and identity', [timer.hasRef(), +timer === id]);
clearTimeout(String(id)); timer.refresh(); line('cleared timeout stays cleared', timer.hasRef());
const disposable = setTimeout(() => line('disposed timer ran', true), 60000);
line('timeout disposal', disposable[Symbol.dispose]() === undefined);
const stopped = setInterval(() => line('disposed interval ran', true), 60000); stopped[Symbol.dispose]();
const fired = setTimeout(function(a, b) { line('timeout receiver', [this === fired, a, b]); }, 1, 'a', 'b');
await delay(10); line('fired timeout ref', fired.hasRef());
const refreshed = new Promise(resolve => {
let calls = 0;
const handle = setTimeout(() => { if (++calls === 1) handle.refresh(); else { line('refresh after firing', calls); resolve(); } }, 1);
}); await refreshed;
}
{
const handle = setImmediate(() => line('cleared immediate ran', true));
line('immediate ref control', [handle.hasRef(), handle.unref() === handle, handle.hasRef(), handle.ref() === handle, handle.hasRef()]);
clearImmediate(handle); line('cleared immediate ref', handle.hasRef());
line('immediate disposal', setImmediate(() => line('disposed immediate ran', true))[Symbol.dispose]() === undefined);
await new Promise(resolve => {
const fired = setImmediate(function(a, b) { line('immediate receiver', [this === fired, a, b, fired.hasRef()]); resolve(); }, 'a', 'b');
});
}
{
line('scheduler prototype', Object.getOwnPropertyNames(Object.getPrototypeOf(scheduler)));
await reject('scheduler constructor', () => new scheduler.constructor());
await reject('scheduler wait receiver', () => scheduler.wait.call({}, 1));
await reject('scheduler yield receiver', () => scheduler.yield.call({}));
const ac = new AbortController(); let signals = 0, refs = 0;
const options = { get signal() { signals++; return ac.signal; }, get ref() { refs++; return true; } };
await delay(1, undefined, options); await immediate(undefined, options);
const iterator = interval(1, undefined, options); await iterator.next(); await iterator.return();
line('option getters', [signals, refs]);
}
}
export function unref() {
const unexpected = () => console.log('unreferenced timer fired');
setTimeout(unexpected, 60000).unref();
setInterval(unexpected, 60000).unref();
setImmediate(unexpected).unref();
delay(60000, undefined, { ref: false }).then(unexpected);
immediate(undefined, { ref: false }).then(unexpected);
interval(60000, undefined, { ref: false }).next().then(unexpected);
scheduler.wait(60000, { ref: false }).then(unexpected);
console.log('scheduled unreferenced timers');
}
@@ -0,0 +1 @@
{"name":"node24-promise-timers-fixture","version":"1.0.0","type":"module","main":"index.js","types":"index.d.ts"}
+3 -3
View File
@@ -119,12 +119,12 @@ describe(`island web globals (scriptc-only${sanitize ? ", sanitized" : ""})`, ()
// Event and buffer.Blob at LOAD); structuredClone joined with the
// globals lane (the HTML StructuredSerialize subset, cycles
// included); WritableStream/FormData/WebSocket stay fenced by
// absence. URL, Buffer, and the immediate-timer globals are installed
// only when the embedded npm module bootstrap runs.
// absence. URL and Buffer are installed only when the embedded npm
// module bootstrap runs; immediate timers use the native check queue.
expect(out).toBe(
"function function function function function function function function function " +
"object object function function function function function function " +
"undefined function undefined undefined undefined undefined undefined undefined",
"undefined function undefined undefined undefined undefined function function",
);
});