Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add promiseWithResolvers #5808

Merged
merged 1 commit into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion yarn-project/foundation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/foundation/src/promise/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './running-promise.js';
export * from './utils.js';
47 changes: 47 additions & 0 deletions yarn-project/foundation/src/promise/running-promise.ts
Original file line number Diff line number Diff line change
@@ -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<void>, 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<void> {
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;
}
}
29 changes: 29 additions & 0 deletions yarn-project/foundation/src/promise/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type PromiseWithResolvers<T> = {
promise: Promise<T>;
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<T>(): PromiseWithResolvers<T> {
// 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<T>((res, rej) => {
resolve = res;
reject = rej;
});

return {
promise,
resolve,
reject,
};
}
61 changes: 1 addition & 60 deletions yarn-project/foundation/src/running-promise/index.ts
Original file line number Diff line number Diff line change
@@ -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<void>, 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<void> {
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';
Loading