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 516dc5b
Show file tree
Hide file tree
Showing 6 changed files with 384 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
294 changes: 294 additions & 0 deletions src/node/internal/internal_timers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
import { validateFunction } from 'node-internal:validators';

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

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, true, 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(): void {
// 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);
}
28 changes: 28 additions & 0 deletions src/node/timers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as _promises from 'node-internal:internal_timers_promises';
import {
setTimeout,
clearTimeout,
setImmediate,
clearImmediate,
setInterval,
clearInterval,
active,
unenroll,
enroll,
} from 'node-internal:internal_timers';

export * from 'node-internal:internal_timers';
export const promises = _promises;

export default {
promises: _promises,
setTimeout,
clearTimeout,
setImmediate,
clearImmediate,
setInterval,
clearInterval,
active,
unenroll,
enroll,
};
3 changes: 3 additions & 0 deletions src/node/timers/promises.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as _default from 'node-internal:internal_timers_promises';
export * from 'node-internal:internal_timers_promises';
export default _default;
Loading

0 comments on commit 516dc5b

Please sign in to comment.