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 16, 2025
1 parent a33c6d2 commit aef241c
Show file tree
Hide file tree
Showing 9 changed files with 455 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
320 changes: 320 additions & 0 deletions src/node/internal/internal_timers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
import { validateFunction, validateNumber } from 'node-internal:validators';
import { ERR_OUT_OF_RANGE } from 'node-internal:internal_errors';

// Timeout values > TIMEOUT_MAX are set to 1.
const TIMEOUT_MAX = 2 ** 31 - 1;

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

function getTimerDuration(msecs: unknown, name: string): number {
validateNumber(msecs, name);
if (msecs < 0 || !Number.isFinite(msecs)) {
throw new ERR_OUT_OF_RANGE(name, 'a non-negative finite number', msecs);
}

// Ensure that msecs fits into signed int32
if (msecs > TIMEOUT_MAX) {
return TIMEOUT_MAX;
}

return msecs;
}

class Timeout {
#timer: string | number | this | undefined;
#callback: (...args: unknown[]) => unknown;
#after: number;
#args: unknown[];
#isRepeat: boolean;
#isRefed: boolean;

public constructor(
callback: (...args: unknown[]) => unknown,
after?: number,
args: unknown[] = [],
isRepeat: boolean = false,
isRefed: boolean = false
) {
if (after === undefined) {
after = 1;
} else {
after *= 1; // Coalesce to number or NaN
}

this.#callback = callback;
this.#after = after;
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.#args),
this.#after
);
} else {
// @ts-expect-error TS2322 Due to difference between Node.js and globals
this.#timer = globalThis.setTimeout(
() => this.#callback(...this.#args),
this.#after
);
}
}

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

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

public unref(): this {
// TODO(soon): Implement this
this.#isRefed = false;
return this;
}

public ref(): this {
// TODO(soon): Implement this
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 {
// @ts-expect-error TS2322 Timer is actually an ID.
return this.#timer;
}

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

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 {
// TODO: Implement this
this.#hasRef = true;
return this;
}

public unref(): this {
// TODO: Implement this
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,
arg1?: unknown,
arg2?: unknown,
arg3?: unknown
): Timeout {
validateFunction(callback, 'callback');

let i, args;
switch (arguments.length) {
// fast cases
case 1:
case 2:
break;
case 3:
args = [arg1];
break;
case 4:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 5; i < arguments.length; i++) {
// eslint-disable-next-line prefer-rest-params
args.push(arguments[i]);
}
break;
}

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

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

export function setImmediate(
callback: (...args: unknown[]) => void,
arg1?: unknown,
arg2?: unknown,
arg3?: unknown
): Immediate {
validateFunction(callback, 'callback');

let i, args;
switch (arguments.length) {
// fast cases
case 1:
break;
case 2:
args = [arg1];
break;
case 3:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++) {
// eslint-disable-next-line prefer-rest-params
args.push(arguments[i]);
}
break;
}

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,
arg1?: unknown,
arg2?: unknown,
arg3?: unknown
): Timeout {
validateFunction(callback, 'callback');

let i, args;
switch (arguments.length) {
// fast cases
case 1:
case 2:
break;
case 3:
args = [arg1];
break;
case 4:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 5; i < arguments.length; i++) {
// eslint-disable-next-line prefer-rest-params
args.push(arguments[i]);
}
break;
}

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);
}
}

/**
* @deprecated Please use setTimeout instead.
*/
export function enroll(_item: unknown, msecs: number): void {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
msecs = getTimerDuration(msecs, 'msecs');
// TODO(soon): Implement this.
throw new Error('Not implemented');
}
57 changes: 57 additions & 0 deletions src/node/internal/internal_timers_promises.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/require-await,@typescript-eslint/no-deprecated,prefer-spread */

import * as timers from 'node-internal:internal_timers';

export async function setTimeout(
...args: Parameters<typeof timers.setTimeout>
): Promise<ReturnType<typeof timers.setTimeout>> {
return timers.setTimeout.apply(timers, args);
}

export async function clearTimeout(
...args: Parameters<typeof timers.clearTimeout>
): Promise<void> {
timers.clearTimeout.apply(timers, args);
}

export async function setImmediate(
...args: Parameters<typeof timers.setImmediate>
): Promise<ReturnType<typeof timers.setImmediate>> {
return timers.setImmediate.apply(timers, args);
}

export async function clearImmediate(
...args: Parameters<typeof timers.clearImmediate>
): Promise<void> {
timers.clearImmediate.apply(timers, args);
}

export async function setInterval(
...args: Parameters<typeof timers.setInterval>
): Promise<ReturnType<typeof timers.setInterval>> {
return timers.setInterval.apply(timers, args);
}

export async function clearInterval(
...args: Parameters<typeof timers.clearInterval>
): Promise<void> {
timers.clearInterval.apply(timers, args);
}

export async function active(
...args: Parameters<typeof timers.active>
): Promise<ReturnType<typeof timers.active>> {
timers.active.apply(timers, args);
}

export async function unenroll(
...args: Parameters<typeof timers.unenroll>
): Promise<void> {
timers.unenroll.apply(timers, args);
}

export async function enroll(
...args: Parameters<typeof timers.enroll>
): Promise<void> {
timers.enroll.apply(timers, args);
}
Loading

0 comments on commit aef241c

Please sign in to comment.