time
js/globals/time.ts
Timer globals and the performance object.
Implements the web-standard timer API:
- setTimeout(fn, ms, ...args) → integer id
- clearTimeout(id)
- setInterval(fn, ms, ...args) → integer id
- clearInterval(id)
- setImmediate(fn, ...args) → integer id
- clearImmediate(id)
- queueMicrotask(fn)
- performance.now() → milliseconds (float, monotonic)
All timer functions are automatically installed on globalThis before
user scripts run, so no import is needed:
setTimeout(() => console.log('hi'), 500);
setImmediate(() => console.log('after this turn'));
Loop integration
Timers are scheduled via internal:runtime/loop, which is a singleton.
Pending timers keep the process alive until they fire: the process exits
only once all pending timers have fired (or been cancelled) and the script
is otherwise done.
Cancellation
clearTimeout / clearInterval mark the timer as cancelled and remove it
from the event loop immediately, so a cancelled timer no longer keeps the
process alive. All timer APIs share one numeric id space, so ids from
setTimeout, setInterval, and setImmediate may be passed to any of the
clear functions interchangeably. Clearing an unknown or already-fired id is
a silent no-op.
performance.now()
Returns the elapsed time in milliseconds since module load, with sub-
millisecond precision. Uses the same high-resolution monotonic clock as
fino:bench:
- macOS: mach_continuous_time() (advances during sleep)
- Linux: clock_gettime(CLOCK_MONOTONIC)
This is intentionally a small Performance subset. Fino exposes EventTarget
dispatch plus now(), timeOrigin, and toJSON(); it does not implement
PerformanceEntry, mark(), measure(), observers, or a performance timeline.
Learn more:
- HTML timers: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers
- High Resolution Time: https://www.w3.org/TR/hr-time-3/
Classes
class Performance extends EventTarget {
Subset of the web Performance API, exposed as the Performance global.
Instances provide:
now()— monotonic milliseconds elapsed since module load (float)timeOrigin— Unix timestamp (ms) captured at module loadtoJSON()— JSON-serializable snapshot- EventTarget methods (
addEventListener,dispatchEvent, ...) inherited from the EventTarget global implementation
PerformanceEntry, mark(), measure(), observers, and the performance
timeline are not part of this runtime subset.
The class exists so performance instanceof Performance and WebIDL-style
brand checks behave as on the web; it cannot be constructed directly.
Calling now(), toJSON(), or the timeOrigin getter with a this that
is not a genuine Performance instance throws a TypeError
(Illegal invocation).
console.log(performance instanceof Performance); // true
const start = performance.now();
// ... work ...
console.log(`took ${performance.now() - start}ms`);
Getters
get timeOrigin()
Unix timestamp in milliseconds captured when this module loaded.
Combine with now() to convert a monotonic reading into an approximate
wall-clock time: timeOrigin + now(). Throws a TypeError if read from a
this that is not a Performance instance.
performance.timeOrigin <= Date.now(); // true
Constructors
constructor(token?: object)
Not user-constructible: throws a TypeError (Illegal constructor) unless
invoked with the module-private token. Use the shared performance
instance instead.
Methods
now()
Return monotonic milliseconds elapsed since module load, with sub-millisecond precision.
The clock is not affected by system clock adjustments, so it is suitable
for measuring durations — not for wall-clock timestamps (use Date.now()
or timeOrigin + now() for those). Throws a TypeError if called on a
this that is not a Performance instance.
const start = performance.now();
const elapsed = performance.now() - start;
toJSON()
Return a JSON-serializable performance snapshot.
Only timeOrigin is included in this subset. Throws a TypeError if
called on a this that is not a Performance instance.
JSON.stringify(performance.toJSON()); // {"timeOrigin":...}
Constants
const performance
The shared Performance instance, installed as globalThis.performance.
This is the only Performance instance in a realm — the constructor is not
user-callable. Its timeOrigin marks when the runtime loaded this module.
const start = performance.now();
await new Promise((resolve) => setTimeout(resolve, 50));
console.log(performance.now() - start); // ≈50 (fractional)
Functions
function setTimeout(fn: (...args: any[]) => void, ms: number = 0, ...args: any[]): number
Schedule fn(...args) to run after at least ms milliseconds.
Negative, NaN, and falsy delays are normalized to 0; a 0 delay fires on the
next loop turn, after the current synchronous code and any pending
microtasks. Extra arguments are forwarded to fn when it fires. The
returned numeric id can be passed to clearTimeout() to cancel the timer
before it fires. Pending timers keep the process alive.
const id = setTimeout((name) => console.log(name), 10, 'timer');
clearTimeout(id);
function clearTimeout(id: number): void
Cancel a pending setTimeout. No-op if id is unknown or already fired.
Cancellation removes the timer from the event loop immediately, so it no
longer keeps the process alive. Clearing the same id more than once is
safe, and non-numeric values like undefined or null are ignored.
const id = setTimeout(() => console.log('late'), 1000);
clearTimeout(id);
function setInterval(fn: (...args: any[]) => void, ms: number = 0, ...args: any[]): number
Repeatedly call fn(...args) every ms milliseconds until cancelled.
The next timeout is scheduled only after the callback returns, so slow
callbacks stretch the effective period rather than piling up. Delay
normalization matches setTimeout: negative, NaN, and falsy delays become
0, which schedules each tick as soon as the loop can run it. The returned
id can be passed to clearInterval(); an interval keeps the process alive
until it is cleared.
let ticks = 0;
const id = setInterval(() => {
if (++ticks === 3) clearInterval(id);
}, 1000);
function clearInterval(id: number): void
Cancel a repeating setInterval. No-op if id is unknown.
Shares the same timer state as clearTimeout(), so ids from either API
can be cleared through either function without throwing. Cancellation is
safe from inside the interval callback itself.
const id = setInterval(() => console.log('tick'), 1000);
clearInterval(id);
function _setTimerRef(id: number, referenced: boolean): void
Change whether a numeric web timer keeps its realm alive. @internal
function _timerHasRef(id: number): boolean
Return whether a numeric web timer keeps its realm alive. @internal
function setImmediate(fn: (...args: any[]) => void, ...args: any[]): number
Schedule fn(...args) to run after the current synchronous turn completes.
Equivalent to setTimeout(fn, 0, ...args): immediate callbacks use the
same runtime loop and numeric id space as timers, and run after pending
microtasks. Extra arguments are forwarded to fn. The returned id can be
passed to clearImmediate() before the callback runs.
const id = setImmediate((name) => console.log(name), 'immediate');
clearImmediate(id);
function clearImmediate(id: number): void
Cancel a pending setImmediate. No-op if id is unknown or already fired.
Alias for clearTimeout() — immediates share the timer id space.
const id = setImmediate(() => console.log('later'));
clearImmediate(id);
function queueMicrotask(fn: () => void): void
Enqueue fn as a microtask — runs before any I/O or timer callbacks but
after the current synchronous code completes.
Equivalent to Promise.resolve().then(fn). Microtasks run in registration
order, and the callback receives no arguments. Throws a TypeError if fn
is not a function.
setTimeout(() => console.log('timer'), 0);
queueMicrotask(() => console.log('microtask')); // logs first