tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Deferred.ts (3159B)


      1 /**
      2 * @license
      3 * Copyright 2024 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 import {TimeoutError} from '../common/Errors.js';
      7 
      8 /**
      9 * @internal
     10 */
     11 export interface DeferredOptions {
     12  message: string;
     13  timeout: number;
     14 }
     15 
     16 /**
     17 * Creates and returns a deferred object along with the resolve/reject functions.
     18 *
     19 * If the deferred has not been resolved/rejected within the `timeout` period,
     20 * the deferred gets resolves with a timeout error. `timeout` has to be greater than 0 or
     21 * it is ignored.
     22 *
     23 * @internal
     24 */
     25 export class Deferred<T, V extends Error = Error> {
     26  static create<R, X extends Error = Error>(
     27    opts?: DeferredOptions,
     28  ): Deferred<R, X> {
     29    return new Deferred<R, X>(opts);
     30  }
     31 
     32  static async race<R>(
     33    awaitables: Array<Promise<R> | Deferred<R>>,
     34  ): Promise<R> {
     35    const deferredWithTimeout = new Set<Deferred<R>>();
     36    try {
     37      const promises = awaitables.map(value => {
     38        if (value instanceof Deferred) {
     39          if (value.#timeoutId) {
     40            deferredWithTimeout.add(value);
     41          }
     42 
     43          return value.valueOrThrow();
     44        }
     45 
     46        return value;
     47      });
     48      // eslint-disable-next-line no-restricted-syntax
     49      return await Promise.race(promises);
     50    } finally {
     51      for (const deferred of deferredWithTimeout) {
     52        // We need to stop the timeout else
     53        // Node.JS will keep running the event loop till the
     54        // timer executes
     55        deferred.reject(new Error('Timeout cleared'));
     56      }
     57    }
     58  }
     59 
     60  #isResolved = false;
     61  #isRejected = false;
     62  #value: T | V | TimeoutError | undefined;
     63  // SAFETY: This is ensured by #taskPromise.
     64  #resolve!: (value: void) => void;
     65  // TODO: Switch to Promise.withResolvers with Node 22
     66  #taskPromise = new Promise<void>(resolve => {
     67    this.#resolve = resolve;
     68  });
     69  #timeoutId: ReturnType<typeof setTimeout> | undefined;
     70  #timeoutError: TimeoutError | undefined;
     71 
     72  constructor(opts?: DeferredOptions) {
     73    if (opts && opts.timeout > 0) {
     74      this.#timeoutError = new TimeoutError(opts.message);
     75      this.#timeoutId = setTimeout(() => {
     76        this.reject(this.#timeoutError!);
     77      }, opts.timeout);
     78    }
     79  }
     80 
     81  #finish(value: T | V | TimeoutError) {
     82    clearTimeout(this.#timeoutId);
     83    this.#value = value;
     84    this.#resolve();
     85  }
     86 
     87  resolve(value: T): void {
     88    if (this.#isRejected || this.#isResolved) {
     89      return;
     90    }
     91    this.#isResolved = true;
     92    this.#finish(value);
     93  }
     94 
     95  reject(error: V | TimeoutError): void {
     96    if (this.#isRejected || this.#isResolved) {
     97      return;
     98    }
     99    this.#isRejected = true;
    100    this.#finish(error);
    101  }
    102 
    103  resolved(): boolean {
    104    return this.#isResolved;
    105  }
    106 
    107  finished(): boolean {
    108    return this.#isResolved || this.#isRejected;
    109  }
    110 
    111  value(): T | V | TimeoutError | undefined {
    112    return this.#value;
    113  }
    114 
    115  #promise: Promise<T> | undefined;
    116  valueOrThrow(): Promise<T> {
    117    if (!this.#promise) {
    118      this.#promise = (async () => {
    119        await this.#taskPromise;
    120        if (this.#isRejected) {
    121          throw this.#value;
    122        }
    123        return this.#value as T;
    124      })();
    125    }
    126    return this.#promise;
    127  }
    128 }