diff --git a/yarn-project/foundation/package.json b/yarn-project/foundation/package.json index 04ceb11d013..df881ec593c 100644 --- a/yarn-project/foundation/package.json +++ b/yarn-project/foundation/package.json @@ -39,7 +39,8 @@ "./noir": "./dest/noir/index.js", "./testing": "./dest/testing/index.js", "./array": "./dest/array/index.js", - "./validation": "./dest/validation/index.js" + "./validation": "./dest/validation/index.js", + "./promise": "./dest/promise/index.js" }, "scripts": { "build": "yarn clean && tsc -b", diff --git a/yarn-project/foundation/src/promise/index.ts b/yarn-project/foundation/src/promise/index.ts new file mode 100644 index 00000000000..8a863fdd488 --- /dev/null +++ b/yarn-project/foundation/src/promise/index.ts @@ -0,0 +1,2 @@ +export * from './running-promise.js'; +export * from './utils.js'; diff --git a/yarn-project/foundation/src/promise/running-promise.ts b/yarn-project/foundation/src/promise/running-promise.ts new file mode 100644 index 00000000000..2c3c7f15c33 --- /dev/null +++ b/yarn-project/foundation/src/promise/running-promise.ts @@ -0,0 +1,47 @@ +import { InterruptibleSleep } from '../sleep/index.js'; + +/** + * RunningPromise is a utility class that helps manage the execution of an asynchronous function + * at a specified polling interval. It allows starting, stopping, and checking the status of the + * internally managed promise. The class also supports interrupting the polling process when stopped. + */ +export class RunningPromise { + private running = false; + private runningPromise = Promise.resolve(); + private interruptibleSleep = new InterruptibleSleep(); + + constructor(private fn: () => Promise, private pollingIntervalMS = 10000) {} + + /** + * Starts the running promise. + */ + public start() { + this.running = true; + + const poll = async () => { + while (this.running) { + await this.fn(); + await this.interruptibleSleep.sleep(this.pollingIntervalMS); + } + }; + this.runningPromise = poll(); + } + + /** + * Stops the running promise, resolves any pending interruptible sleep, + * and waits for the currently executing function to complete. + */ + async stop(): Promise { + this.running = false; + this.interruptibleSleep.interrupt(); + await this.runningPromise; + } + + /** + * Checks if the running promise is currently active. + * @returns True if the promise is running. + */ + public isRunning() { + return this.running; + } +} diff --git a/yarn-project/foundation/src/promise/utils.ts b/yarn-project/foundation/src/promise/utils.ts new file mode 100644 index 00000000000..fd48f3b0b99 --- /dev/null +++ b/yarn-project/foundation/src/promise/utils.ts @@ -0,0 +1,29 @@ +export type PromiseWithResolvers = { + promise: Promise; + resolve: (value: T) => void; + reject: (reason?: any) => void; +}; + +/** + * A polyfill for the Promise.withResolvers proposed API. + * @see https://github.com/tc39/proposal-promise-with-resolvers + * @returns A promise with resolvers. + */ +export function promiseWithResolvers(): PromiseWithResolvers { + // use ! operator to avoid TS error + let resolve!: (value: T) => void; + let reject!: (reason?: any) => void; + + // the ES spec guarantees that the promise executor is called synchronously + // so the resolve and reject functions will be defined + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { + promise, + resolve, + reject, + }; +} diff --git a/yarn-project/foundation/src/running-promise/index.ts b/yarn-project/foundation/src/running-promise/index.ts index d362ebe2ad1..24ae1a30bed 100644 --- a/yarn-project/foundation/src/running-promise/index.ts +++ b/yarn-project/foundation/src/running-promise/index.ts @@ -1,60 +1 @@ -/** - * RunningPromise is a utility class that helps manage the execution of an asynchronous function - * at a specified polling interval. It allows starting, stopping, and checking the status of the - * internally managed promise. The class also supports interrupting the polling process when stopped. - */ -export class RunningPromise { - private running = false; - private runningPromise = Promise.resolve(); - private interruptPromise = Promise.resolve(); - private interruptResolve = () => {}; - constructor(private fn: () => Promise, private pollingInterval = 10000) {} - - /** - * Starts the running promise. - */ - public start() { - this.running = true; - this.interruptPromise = new Promise(resolve => (this.interruptResolve = resolve)); - - const poll = async () => { - while (this.running) { - await this.fn(); - await this.interruptibleSleep(this.pollingInterval); - } - }; - this.runningPromise = poll(); - } - - /** - * Stops the running promise, resolves any pending interruptible sleep, - * and waits for the currently executing function to complete. - */ - async stop(): Promise { - this.running = false; - this.interruptResolve(); - await this.runningPromise; - } - - /** - * A sleep function that can be interrupted before the specified time. - * The sleep duration is determined by 'timeInMs', and it can be terminated early if the 'interruptPromise' is resolved. - * @param timeInMs - The time in milliseconds. - */ - private async interruptibleSleep(timeInMs: number) { - let timeout!: NodeJS.Timeout; - const sleepPromise = new Promise(resolve => { - timeout = setTimeout(resolve, timeInMs); - }); - await Promise.race([sleepPromise, this.interruptPromise]); - clearTimeout(timeout); - } - - /** - * Checks if the running promise is currently active. - * @returns True if the promise is running. - */ - public isRunning() { - return this.running; - } -} +export * from '../promise/running-promise.js';