nodejs/stdlib/timers.rs
1//! Node `timers` and `timers/promises` modules.
2//!
3//! `require('timers')` re-exports the SAME timer primitives that already exist as
4//! globals (`setTimeout`/`setInterval`/`setImmediate` + their `clear*`), so this
5//! module owns NO queue of its own: every method delegates straight to
6//! `builtins::call_builtin_function`, which schedules onto the single shared
7//! `JsHost.macrotasks` queue. `timers.foo(...)` is therefore observably identical
8//! to the global `foo(...)`.
9//!
10//! `require('timers/promises')` returns the promise-based variants: `setTimeout`
11//! and `setImmediate` resolve a Promise after the delay instead of invoking a
12//! callback. They are built on the SAME two substrates — the global timer
13//! scheduler and the `@@presolve:<id>` native-continuation convention that
14//! `builtins.rs` uses for Promise resolve reactions — so no new mechanism is
15//! introduced: a timer is scheduled whose callback is the promise's resolver.
16
17use super::arg_num;
18use crate::host::{with_host, JsObj};
19use fusevm::Value;
20
21// ── timers (callback API) ────────────────────────────────────────────────────
22
23/// Methods of the `timers` module. Each name is also a global; `call` forwards to
24/// the identical global implementation, so there is one timer queue, not two.
25pub const METHODS: &[&str] = &[
26 "setTimeout",
27 "setInterval",
28 "setImmediate",
29 "clearTimeout",
30 "clearInterval",
31 "clearImmediate",
32];
33
34/// Dispatch a `timers.<method>` call by delegating to the matching global timer
35/// builtin. `clearImmediate` has no distinct global handler (the loop cancels by
36/// id regardless of kind), so it maps to `clearTimeout`.
37pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
38 let global = match method {
39 "setTimeout" | "setInterval" | "setImmediate" | "clearTimeout" | "clearInterval" => method,
40 // No separate global `clearImmediate` handler exists; cancellation is by
41 // timer id in both cases, so route it through `clearTimeout`.
42 "clearImmediate" => "clearTimeout",
43 _ => return None,
44 };
45 Some(crate::builtins::call_builtin_function(
46 global,
47 args.to_vec(),
48 ))
49}
50
51// ── timers/promises (Promise API) ────────────────────────────────────────────
52
53/// Methods of the `timers/promises` module (its namespace name carries no `.`, so
54/// `stdlib::is_method` treats the whole `"timers/promises"` as the namespace).
55///
56/// `setInterval(delay[, value])` (an async iterator) is NOT implemented: the
57/// `for await` machinery finds a native object's async iterator only via
58/// `host::user_async_iterator_fn`, which needs a *callable* `@@asyncIterator`
59/// stored property discoverable by `lookup_chain` — native-tagged objects
60/// dispatch methods through the parent `instance_call` table, not stored
61/// properties, so there is no way to expose it without editing `builtins.rs`/
62/// `host.rs` (out of scope here).
63pub const PROMISES_METHODS: &[&str] = &["setTimeout", "setImmediate"];
64
65/// Dispatch a `timers/promises.<method>` call.
66///
67/// `setTimeout(delay[, value])` → a Promise that fulfills with `value` (undefined
68/// if absent) after `delay` ms. `setImmediate([value])` → a Promise that fulfills
69/// with `value` on the next loop turn. Any trailing `options` argument (Node's
70/// `{ signal, ref }`) is accepted and ignored — abort/unref are not modeled.
71pub fn promises_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
72 match method {
73 "setTimeout" => {
74 let delay = arg_num(args, 0);
75 let value = args.get(1).cloned().unwrap_or(Value::Undef);
76 Some(Ok(schedule_promise("setTimeout", Some(delay), value)))
77 }
78 "setImmediate" => {
79 let value = args.first().cloned().unwrap_or(Value::Undef);
80 Some(Ok(schedule_promise("setImmediate", None, value)))
81 }
82 _ => None,
83 }
84}
85
86/// Allocate a pending Promise and schedule its resolution with `value` via the
87/// existing global timer scheduler. The scheduled callback is a
88/// `Builtin("@@presolve:<id>")` value — the same native continuation
89/// `builtins.rs` invokes to fulfill a Promise — so when the timer fires it
90/// resolves the Promise with the timer's extra argument (`value`).
91fn schedule_promise(kind: &str, delay: Option<f64>, value: Value) -> Value {
92 // Create the promise and grab its id for the resolver continuation.
93 let (promise, id) = with_host(|h| {
94 let p = h.new_promise();
95 let id = h.promise_id(&p).unwrap_or(0);
96 (p, id)
97 });
98 // The resolver: invoked with `[value]` when the timer fires.
99 let resolver = with_host(|h| h.alloc(JsObj::Builtin(format!("@@presolve:{id}"))));
100 // Route through the exact global scheduler. `setTimeout(cb, delay, value)`
101 // and `setImmediate(cb, value)` pass `value` on to the callback as its first
102 // argument, which `@@presolve:<id>` resolves the promise with.
103 let timer_args = match delay {
104 Some(d) => vec![resolver, Value::Float(d), value],
105 None => vec![resolver, value],
106 };
107 // Ignore the returned timer id; the promise is the module's return value.
108 let _ = crate::builtins::call_builtin_function(kind, timer_args);
109 promise
110}