deno.land / x / deno@v1.28.2 / ext / web / 02_timers.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license."use strict";
((window) => { const core = window.Deno.core; const ops = core.ops; const { ArrayPrototypePush, ArrayPrototypeShift, FunctionPrototypeCall, Map, MapPrototypeDelete, MapPrototypeGet, MapPrototypeHas, MapPrototypeSet, Uint8Array, Uint32Array, // deno-lint-ignore camelcase NumberPOSITIVE_INFINITY, PromisePrototypeThen, SafeArrayIterator, SymbolFor, TypeError, indirectEval, } = window.__bootstrap.primordials; const { webidl } = window.__bootstrap; const { reportException } = window.__bootstrap.event; const { assert } = window.__bootstrap.infra;
const hrU8 = new Uint8Array(8); const hr = new Uint32Array(hrU8.buffer); function opNow() { ops.op_now(hrU8); return (hr[0] * 1000 + hr[1] / 1e6); }
// ---------------------------------------------------------------------------
/** * The task queue corresponding to the timer task source. * * @type { {action: () => void, nestingLevel: number}[] } */ const timerTasks = [];
/** * The current task's timer nesting level, or zero if we're not currently * running a timer task (since the minimum nesting level is 1). * * @type {number} */ let timerNestingLevel = 0;
function handleTimerMacrotask() { if (timerTasks.length === 0) { return true; }
const task = ArrayPrototypeShift(timerTasks);
timerNestingLevel = task.nestingLevel;
try { task.action(); } finally { timerNestingLevel = 0; } return timerTasks.length === 0; }
// ---------------------------------------------------------------------------
/** * The keys in this map correspond to the key ID's in the spec's map of active * timers. The values are the timeout's cancel rid. * * @type {Map<number, { cancelRid: number, isRef: boolean, promiseId: number }>} */ const activeTimers = new Map();
let nextId = 1;
/** * @param {Function | string} callback * @param {number} timeout * @param {Array<any>} args * @param {boolean} repeat * @param {number | undefined} prevId * @returns {number} The timer ID */ function initializeTimer( callback, timeout, args, repeat, prevId, ) { // 2. If previousId was given, let id be previousId; otherwise, let // previousId be an implementation-defined integer than is greater than zero // and does not already exist in global's map of active timers. let id; let timerInfo; if (prevId !== undefined) { // `prevId` is only passed for follow-up calls on intervals assert(repeat); id = prevId; timerInfo = MapPrototypeGet(activeTimers, id); } else { // TODO(@andreubotella): Deal with overflow. // https://github.com/whatwg/html/issues/7358 id = nextId++; const cancelRid = ops.op_timer_handle(); timerInfo = { cancelRid, isRef: true, promiseId: -1 };
// Step 4 in "run steps after a timeout". MapPrototypeSet(activeTimers, id, timerInfo); }
// 3. If the surrounding agent's event loop's currently running task is a // task that was created by this algorithm, then let nesting level be the // task's timer nesting level. Otherwise, let nesting level be zero. // 4. If timeout is less than 0, then set timeout to 0. // 5. If nesting level is greater than 5, and timeout is less than 4, then // set timeout to 4. // // The nesting level of 5 and minimum of 4 ms are spec-mandated magic // constants. if (timeout < 0) timeout = 0; if (timerNestingLevel > 5 && timeout < 4) timeout = 4;
// 9. Let task be a task that runs the following steps: const task = { action: () => { // 1. If id does not exist in global's map of active timers, then abort // these steps. // // This is relevant if the timer has been canceled after the sleep op // resolves but before this task runs. if (!MapPrototypeHas(activeTimers, id)) { return; }
// 2. // 3. if (typeof callback === "function") { try { FunctionPrototypeCall( callback, globalThis, ...new SafeArrayIterator(args), ); } catch (error) { reportException(error); } } else { indirectEval(callback); }
if (repeat) { if (MapPrototypeHas(activeTimers, id)) { // 4. If id does not exist in global's map of active timers, then // abort these steps. // NOTE: If might have been removed via the author code in handler // calling clearTimeout() or clearInterval(). // 5. If repeat is true, then perform the timer initialization steps // again, given global, handler, timeout, arguments, true, and id. initializeTimer(callback, timeout, args, true, id); } } else { // 6. Otherwise, remove global's map of active timers[id]. core.tryClose(timerInfo.cancelRid); MapPrototypeDelete(activeTimers, id); } },
// 10. Increment nesting level by one. // 11. Set task's timer nesting level to nesting level. nestingLevel: timerNestingLevel + 1, };
// 12. Let completionStep be an algorithm step which queues a global task on // the timer task source given global to run task. // 13. Run steps after a timeout given global, "setTimeout/setInterval", // timeout, completionStep, and id. runAfterTimeout( () => ArrayPrototypePush(timerTasks, task), timeout, timerInfo, );
return id; }
// ---------------------------------------------------------------------------
/** * @typedef ScheduledTimer * @property {number} millis * @property {() => void} cb * @property {boolean} resolved * @property {ScheduledTimer | null} prev * @property {ScheduledTimer | null} next */
/** * A doubly linked list of timers. * @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } } */ const scheduledTimers = { head: null, tail: null };
/** * @param {() => void} cb Will be run after the timeout, if it hasn't been * cancelled. * @param {number} millis * @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo */ function runAfterTimeout(cb, millis, timerInfo) { const cancelRid = timerInfo.cancelRid; const sleepPromise = core.opAsync("op_sleep", millis, cancelRid); timerInfo.promiseId = sleepPromise[SymbolFor("Deno.core.internalPromiseId")]; if (!timerInfo.isRef) { core.unrefOp(timerInfo.promiseId); }
/** @type {ScheduledTimer} */ const timerObject = { millis, cb, resolved: false, prev: scheduledTimers.tail, next: null, };
// Add timerObject to the end of the list. if (scheduledTimers.tail === null) { assert(scheduledTimers.head === null); scheduledTimers.head = scheduledTimers.tail = timerObject; } else { scheduledTimers.tail.next = timerObject; scheduledTimers.tail = timerObject; }
// 1. PromisePrototypeThen( sleepPromise, (cancelled) => { if (!cancelled) { // The timer was cancelled. removeFromScheduledTimers(timerObject); return; } // 2. Wait until any invocations of this algorithm that had the same // global and orderingIdentifier, that started before this one, and // whose milliseconds is equal to or less than this one's, have // completed. // 4. Perform completionSteps.
// IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the // right order, whenever one resolves, we run through the scheduled // timers list (which is in the order in which they were scheduled), and // we call the callback for every timer which both: // a) has resolved, and // b) its timeout is lower than the lowest unresolved timeout found so // far in the list.
timerObject.resolved = true;
let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY;
let currentEntry = scheduledTimers.head; while (currentEntry !== null) { if (currentEntry.millis < lowestUnresolvedTimeout) { if (currentEntry.resolved) { currentEntry.cb(); removeFromScheduledTimers(currentEntry); } else { lowestUnresolvedTimeout = currentEntry.millis; } }
currentEntry = currentEntry.next; } }, ); }
/** @param {ScheduledTimer} timerObj */ function removeFromScheduledTimers(timerObj) { if (timerObj.prev !== null) { timerObj.prev.next = timerObj.next; } else { assert(scheduledTimers.head === timerObj); scheduledTimers.head = timerObj.next; } if (timerObj.next !== null) { timerObj.next.prev = timerObj.prev; } else { assert(scheduledTimers.tail === timerObj); scheduledTimers.tail = timerObj.prev; } }
// ---------------------------------------------------------------------------
function checkThis(thisArg) { if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { throw new TypeError("Illegal invocation"); } }
function setTimeout(callback, timeout = 0, ...args) { checkThis(this); if (typeof callback !== "function") { callback = webidl.converters.DOMString(callback); } timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, false); }
function setInterval(callback, timeout = 0, ...args) { checkThis(this); if (typeof callback !== "function") { callback = webidl.converters.DOMString(callback); } timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, true); }
function clearTimeout(id = 0) { checkThis(this); id = webidl.converters.long(id); const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo !== undefined) { core.tryClose(timerInfo.cancelRid); MapPrototypeDelete(activeTimers, id); } }
function clearInterval(id = 0) { checkThis(this); clearTimeout(id); }
function refTimer(id) { const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo === undefined || timerInfo.isRef) { return; } timerInfo.isRef = true; core.refOp(timerInfo.promiseId); }
function unrefTimer(id) { const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo === undefined || !timerInfo.isRef) { return; } timerInfo.isRef = false; core.unrefOp(timerInfo.promiseId); }
window.__bootstrap.timers = { setTimeout, setInterval, clearTimeout, clearInterval, handleTimerMacrotask, opNow, refTimer, unrefTimer, };})(this);
Version Info