Skip to content

Commit

Permalink
add node:timers module
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Jan 17, 2025
1 parent 5377f9f commit aa66c19
Show file tree
Hide file tree
Showing 9 changed files with 687 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/node/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ wd_ts_bundle(
"stream/*.js",
"path/*.ts",
"util/*.ts",
"timers/*.ts",
]),
schema_id = "0xbcc8f57c63814005",
tsconfig_json = "tsconfig.json",
Expand Down
252 changes: 252 additions & 0 deletions src/node/internal/internal_timers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
//
// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

import { validateFunction } from 'node-internal:validators';

let clearTimeoutImpl: (obj: Timeout) => void;
let clearImmediateImpl: (obj: Immediate) => void;

export class Timeout {
// @ts-expect-error TS2564 It has a default value, but TS fails to detect it.
#timer: number;
#callback: (...args: unknown[]) => unknown;
#after: number;
#args: unknown[];
#isRepeat: boolean;
#isRefed: boolean;

public constructor(
callback: (...args: unknown[]) => unknown,
after: number = 1,
args: unknown[] = [],
isRepeat: boolean = false,
isRefed: boolean = false
) {
this.#callback = callback;
// Left it as multiply by 1 due to make the behavior as similar to Node.js
// as possible.
this.#after = after * 1;
this.#args = args;
this.#isRepeat = isRepeat;
this.#isRefed = isRefed;
this.#constructTimer();
}

#constructTimer(): void {
if (this.#isRepeat) {
// @ts-expect-error TS2322 Due to difference between Node.js and globals
this.#timer = globalThis.setInterval(
this.#callback,
this.#after,
...this.#args
);
} else {
// @ts-expect-error TS2322 Due to difference between Node.js and globals
this.#timer = globalThis.setTimeout(
this.#callback,
this.#after,
...this.#args
);
}
}

#clearTimeout(): void {
if (this.#isRepeat) {
globalThis.clearInterval(this.#timer);
} else {
globalThis.clearTimeout(this.#timer);
}
}

public refresh(): this {
this.#clearTimeout();
this.#constructTimer();
return this;
}

public unref(): this {
// Intentionally left as no-op.
this.#isRefed = false;
return this;
}

public ref(): this {
// Intentionally left as no-op.
this.#isRefed = true;
return this;
}

public hasRef(): boolean {
return this.#isRefed;
}

public close(): this {
this.#clearTimeout();
return this;
}

public [Symbol.dispose](): void {
this.#clearTimeout();
}

public [Symbol.toPrimitive](): number {
return this.#timer;
}

static {
clearTimeoutImpl = (obj: Timeout): void => {
obj.#clearTimeout();
};
}
}

export class Immediate {
// @ts-expect-error TS2724 Node.js and global difference.
#timer: globalThis.Immediate;
#hasRef: boolean = false;

public constructor(
callback: (...args: unknown[]) => void,
args: unknown[] = []
) {
this.#timer = globalThis.setImmediate(callback, ...args);
}

public ref(): this {
// Intentionally left as no-op.
this.#hasRef = true;
return this;
}

public unref(): this {
// Intentionally left as no-op.
this.#hasRef = false;
return this;
}

public hasRef(): boolean {
return this.#hasRef;
}

public [Symbol.dispose](): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
globalThis.clearImmediate(this.#timer);
}

static {
clearImmediateImpl = (obj: Immediate): void => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
globalThis.clearImmediate(obj.#timer);
};
}
}

export function setTimeout(
callback: (...args: unknown[]) => unknown,
after: number,
...args: unknown[]
): Timeout {
validateFunction(callback, 'callback');

return new Timeout(
callback,
after,
args,
/* isRepeat */ false,
/* isRefed */ true
);
}

export function clearTimeout(timer: unknown): void {
if (timer instanceof Timeout) {
clearTimeoutImpl(timer);
} else if (typeof timer === 'number') {
globalThis.clearTimeout(timer);
}
}

export function setImmediate(
callback: (...args: unknown[]) => void,
...args: unknown[]
): Immediate {
validateFunction(callback, 'callback');
return new Immediate(callback, args);
}

export function clearImmediate(immediate?: Immediate): void {
if (immediate != null) {
clearImmediateImpl(immediate);
}
}

export function setInterval(
callback: (...args: unknown[]) => void,
repeat: number,
...args: unknown[]
): Timeout {
validateFunction(callback, 'callback');
return new Timeout(
callback,
repeat,
args,
/* isRepeat */ true,
/* isRefed */ true
);
}

export function clearInterval(timer: unknown): void {
if (timer instanceof Timeout) {
clearTimeoutImpl(timer);
} else if (typeof timer === 'number') {
globalThis.clearInterval(timer);
}
}

/**
* @deprecated Please use timeout.refresh() instead.
*/
export function active(timer: unknown): void {
if (timer instanceof Timeout) {
timer.refresh();
}
}

/**
* @deprecated Please use clearTimeout instead.
*/
export function unenroll(timer: unknown): void {
if (timer instanceof Timeout) {
clearTimeoutImpl(timer);
} else if (typeof timer === 'number') {
globalThis.clearTimeout(timer);
}
}

/**
* @deprecated Please use setTimeout instead.
*/
export function enroll(_item: unknown, _msecs: number): void {
throw new Error('Not implemented. Please use setTimeout() instead.');
}
Loading

0 comments on commit aa66c19

Please sign in to comment.