Skip to content
File

Blob: src/node/internal/internal_timers.ts

typescript216 lines
1// Copyright (c) 2017-2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import { validateFunction } from 'node-internal:validators';
27import { default as timersUtil } from 'node-internal:timers';
28 
29// Capture original global timer functions at module load time, before they might be
30// overridden by the enable_nodejs_global_timers compat flag. This avoids circular
31// dependency issues when our setTimeout/setInterval return Timeout objects.
32// Cast to number-returning functions since we know we're capturing the original
33// implementations before any override.
34const originalSetTimeout = globalThis.setTimeout.bind(globalThis) as (
35 callback: (...args: unknown[]) => unknown,
36 ms?: number,
37 ...args: unknown[]
38) => number;
39const originalSetInterval = globalThis.setInterval.bind(globalThis) as (
40 callback: (...args: unknown[]) => unknown,
41 ms?: number,
42 ...args: unknown[]
43) => number;
44const originalClearTimeout = globalThis.clearTimeout.bind(globalThis) as (
45 id?: number
46) => void;
47const originalClearInterval = globalThis.clearInterval.bind(globalThis) as (
48 id?: number
49) => void;
50 
51let clearTimeoutImpl: (obj: Timeout) => void;
52 
53export class Timeout {
54 #timer: number;
55 #callback: (...args: unknown[]) => unknown;
56 #after: number;
57 #args: unknown[];
58 #isRepeat: boolean;
59 #isRefed: boolean;
60 
61 constructor(
62 callback: (...args: unknown[]) => unknown,
63 after: number = 1,
64 args: unknown[] = [],
65 isRepeat: boolean = false,
66 isRefed: boolean = false
67 ) {
68 this.#callback = callback;
69 // Left it as multiply by 1 due to make the behavior as similar to Node.js
70 // as possible.
71 this.#after = after * 1;
72 this.#args = args;
73 this.#isRepeat = isRepeat;
74 this.#isRefed = isRefed;
75 this.#timer = this.#constructTimer();
76 }
77 
78 #constructTimer(): number {
79 this.#timer = this.#isRepeat
80 ? originalSetInterval(this.#callback, this.#after, ...this.#args)
81 : originalSetTimeout(this.#callback, this.#after, ...this.#args);
82 return this.#timer;
83 }
84 
85 #clearTimeout(): void {
86 if (this.#isRepeat) {
87 originalClearInterval(this.#timer);
88 } else {
89 originalClearTimeout(this.#timer);
90 }
91 }
92 
93 refresh(): this {
94 this.#clearTimeout();
95 this.#constructTimer();
96 return this;
97 }
98 
99 unref(): this {
100 // Intentionally left as no-op.
101 this.#isRefed = false;
102 return this;
103 }
104 
105 ref(): this {
106 // Intentionally left as no-op.
107 this.#isRefed = true;
108 return this;
109 }
110 
111 hasRef(): boolean {
112 return this.#isRefed;
113 }
114 
115 close(): this {
116 this.#clearTimeout();
117 return this;
118 }
119 
120 [Symbol.dispose](): void {
121 this.#clearTimeout();
122 }
123 
124 [Symbol.toPrimitive](): number {
125 return this.#timer;
126 }
127 
128 static {
129 clearTimeoutImpl = (obj: Timeout): void => {
130 obj.#clearTimeout();
131 };
132 }
133}
134 
135export function setTimeout<TArgs extends unknown[]>(
136 callback: (...args: TArgs) => unknown,
137 delay?: number,
138 ...args: TArgs
139): Timeout {
140 validateFunction(callback, 'callback');
141 
142 return new Timeout(
143 callback,
144 delay,
145 args,
146 /* isRepeat */ false,
147 /* isRefed */ true
148 );
149}
150 
151export function clearTimeout(
152 timer: Timeout | string | number | undefined
153): void {
154 if (timer instanceof Timeout) {
155 clearTimeoutImpl(timer);
156 } else if (typeof timer === 'number') {
157 originalClearTimeout(timer);
158 }
159}
160 
161export const setImmediate = timersUtil.setImmediate.bind(timersUtil);
162 
163export const clearImmediate = timersUtil.clearImmediate.bind(timersUtil);
164 
165export function setInterval<TArgs extends unknown[]>(
166 callback: (...args: TArgs) => void,
167 repeat?: number,
168 ...args: TArgs
169): Timeout {
170 validateFunction(callback, 'callback');
171 return new Timeout(
172 callback,
173 repeat,
174 args,
175 /* isRepeat */ true,
176 /* isRefed */ true
177 );
178}
179 
180export function clearInterval(
181 timer: Timeout | string | number | undefined
182): void {
183 if (timer instanceof Timeout) {
184 clearTimeoutImpl(timer);
185 } else if (typeof timer === 'number') {
186 originalClearInterval(timer);
187 }
188}
189 
190/**
191 * @deprecated Please use timeout.refresh() instead.
192 */
193export function active(timer: Timeout | string | number | undefined): void {
194 if (timer instanceof Timeout) {
195 timer.refresh();
196 }
197}
198 
199/**
200 * @deprecated Please use clearTimeout instead.
201 */
202export function unenroll(timer: unknown): void {
203 if (timer instanceof Timeout) {
204 clearTimeoutImpl(timer);
205 } else if (typeof timer === 'number') {
206 originalClearTimeout(timer);
207 }
208}
209 
210/**
211 * @deprecated Please use setTimeout instead.
212 */
213export function enroll(_item: unknown, _msecs: number): void {
214 throw new Error('Not implemented. Please use setTimeout() instead.');
215}