Skip to main content

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}