// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import { CancellationTokenSource, Disposable } from "vscode";
/**
* Utilities for working with promises.
*/
export class PromiseUtil {
public static async forEach<T>(
sources: T[],
promiseGenerator: (source: T) => Promise<void>,
): Promise<void> {
await Promise.all(sources.map(source => promiseGenerator(source)));
}
/**
* Retries an operation a given number of times. For each retry, a condition is checked.
* If the condition is not satisfied after the maximum number of retries, and error is thrown.
* Otherwise, the result of the operation is returned once the condition is satisfied.
*
* @param operation - the function to execute.
* @param condition - the condition to check between iterations.
* @param maxRetries - the maximum number of retries.
* @param delay - time between iterations, in milliseconds.
* @param failure - error description.
*/
public static retryAsync<T>(
operation: () => Promise<T>,
condition: (result: T) => boolean | Promise<boolean>,
maxRetries: number,
delay: number,
failure: string,
cancellationTokenSource?: CancellationTokenSource,
): Promise<T> {
return this.retryAsyncIteration(
operation,
condition,
maxRetries,
0,
delay,
failure,
cancellationTokenSource,
);
}
public static async reduce<T>(
sources: T[] | Promise<T[]>,
generateAsyncOperation: (value: T) => Promise<void>,
): Promise<void> {
const arraySources: T[] = sources instanceof Promise ? await sources : sources;
return arraySources.reduce(async (previousReduction: Promise<void>, newSource: T) => {
await previousReduction;
return generateAsyncOperation(newSource);
}, Promise.resolve());
}
public static async delay(duration: number): Promise<void> {
return new Promise<void>(resolve => {
setTimeout(resolve, duration);
});
}
public static waitUntil<T>(
condition: () => Promise<T | null> | T | null,
interval: number = 1000,
timeout?: number,
): Promise<T | null> {
return new Promise(async (resolve, reject) => {
let rejectTimeout: NodeJS.Timeout | undefined;
// eslint-disable-next-line prefer-const
let сheckInterval: NodeJS.Timeout | undefined;
if (timeout) {
rejectTimeout = setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
cleanup();
resolve(null);
}, timeout);
}
const cleanup = () => {
if (rejectTimeout) {
clearTimeout(rejectTimeout);
}
if (сheckInterval) {
clearInterval(сheckInterval);
}
};
const tryToResolve = async (): Promise<boolean> => {
try {
const result = await condition();
if (result) {
cleanup();
resolve(result);
}
return !!result;
} catch (err) {
cleanup();
reject(err);
return false;
}
};
const resolved = await tryToResolve();
if (resolved) {
return;
}
сheckInterval = setInterval(async () => {
await tryToResolve();
}, interval);
});
}
public static promiseCacheDecorator<T>(
func: (...args: any[]) => Promise<T>,
context: Record<string, any> | null = null,
): (...args: any[]) => Promise<T> {
let promise: Promise<T> | undefined;
return (...args: any[]): Promise<T> => {
if (!promise) {
promise = func.apply(context, args) as Promise<T>;
}
return promise;
};
}
private static async retryAsyncIteration<T>(
operation: () => Promise<T>,
condition: (result: T) => boolean | Promise<boolean>,
maxRetries: number,
iteration: number,
delay: number,
failure: string,
cancellationTokenSource?: CancellationTokenSource,
): Promise<T> {
const result = await operation();
const conditionResult = await condition(result);
if (conditionResult) {
return result;
}
if (
iteration < maxRetries &&
!(cancellationTokenSource && cancellationTokenSource.token.isCancellationRequested)
) {
await PromiseUtil.delay(delay);
return this.retryAsyncIteration(
operation,
condition,
maxRetries,
iteration + 1,
delay,
failure,
cancellationTokenSource,
);
}
throw new Error(failure);
}
}
export class Delayer<T> implements Disposable {
private timeout: any;
private completionPromise: Promise<any> | null;
private doResolve: ((value?: any | Promise<any>) => void) | null;
private doReject: ((err: any) => void) | null;
private task: { (): T | Promise<T> } | null;
constructor() {
this.timeout = null;
this.completionPromise = null;
this.doResolve = null;
this.doReject = null;
this.task = null;
}
public runWihtDelay(task: { (): T | Promise<T> }, delay: number): Promise<T> {
this.task = task;
this.cancelTimeout();
if (!this.completionPromise) {
this.completionPromise = new Promise((resolve, reject) => {
this.doResolve = resolve;
this.doReject = reject;
}).then(() => {
this.completionPromise = null;
this.doResolve = null;
if (this.task) {
const task = this.task;
this.task = null;
return task();
}
return undefined;
});
}
this.timeout = setTimeout(() => {
this.timeout = null;
if (this.doResolve) {
this.doResolve(null);
}
}, delay);
return this.completionPromise;
}
public isRunning(): boolean {
return this.timeout !== null;
}
public cancel(): void {
this.cancelTimeout();
if (this.completionPromise) {
if (this.doReject) {
this.doReject(new Error("Canceled"));
}
this.completionPromise = null;
}
}
public dispose(): void {
this.cancel();
}
private cancelTimeout(): void {
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
}microsoft/vscode-react-native
Publicmirrored from https://github.com/microsoft/vscode-react-nativeAvailable
src/common/node/promise.ts
235lines · modepreview