From 93fb2f4ea4d2cffd5ce3617a2795958329a65630 Mon Sep 17 00:00:00 2001 From: jrandolf <101637635+jrandolf@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:40:26 +0100 Subject: [PATCH] refactor: make `Deferred.valueOrThrow` idempotent. (#11657) --- packages/puppeteer-core/src/util/Deferred.ts | 131 ++++++++++--------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/packages/puppeteer-core/src/util/Deferred.ts b/packages/puppeteer-core/src/util/Deferred.ts index a512a928728..0dfb013bb3f 100644 --- a/packages/puppeteer-core/src/util/Deferred.ts +++ b/packages/puppeteer-core/src/util/Deferred.ts @@ -18,70 +18,9 @@ export interface DeferredOptions { * @internal */ export class Deferred { - #isResolved = false; - #isRejected = false; - #value: T | V | TimeoutError | undefined; - #resolver: (value: void) => void = () => {}; - #taskPromise = new Promise(resolve => { - this.#resolver = resolve; - }); - #timeoutId: ReturnType | undefined; - #timeoutError: TimeoutError | undefined; - - constructor(opts?: DeferredOptions) { - if (opts && opts.timeout > 0) { - this.#timeoutError = new TimeoutError(opts.message); - this.#timeoutId = setTimeout(() => { - this.reject(this.#timeoutError!); - }, opts.timeout); - } - } - - #finish(value: T | V | TimeoutError) { - clearTimeout(this.#timeoutId); - this.#value = value; - this.#resolver(); - } - - resolve(value: T): void { - if (this.#isRejected || this.#isResolved) { - return; - } - this.#isResolved = true; - this.#finish(value); - } - - reject(error: V | TimeoutError): void { - if (this.#isRejected || this.#isResolved) { - return; - } - this.#isRejected = true; - this.#finish(error); - } - - resolved(): boolean { - return this.#isResolved; - } - - finished(): boolean { - return this.#isResolved || this.#isRejected; - } - - value(): T | V | TimeoutError | undefined { - return this.#value; - } - - async valueOrThrow(): Promise { - await this.#taskPromise; - if (this.#isRejected) { - throw this.#value; - } - return this.#value as T; - } - static create( opts?: DeferredOptions - ): Deferred { + ): Deferred { return new Deferred(opts); } @@ -112,4 +51,72 @@ export class Deferred { } } } + + #isResolved = false; + #isRejected = false; + #value: T | V | TimeoutError | undefined; + // SAFETY: This is ensured by #taskPromise. + #resolve!: (value: void) => void; + #taskPromise = new Promise(resolve => { + this.#resolve = resolve; + }); + #timeoutId: ReturnType | undefined; + #timeoutError: TimeoutError | undefined; + + constructor(opts?: DeferredOptions) { + if (opts && opts.timeout > 0) { + this.#timeoutError = new TimeoutError(opts.message); + this.#timeoutId = setTimeout(() => { + this.reject(this.#timeoutError!); + }, opts.timeout); + } + } + + #finish(value: T | V | TimeoutError) { + clearTimeout(this.#timeoutId); + this.#value = value; + this.#resolve(); + } + + resolve(value: T): void { + if (this.#isRejected || this.#isResolved) { + return; + } + this.#isResolved = true; + this.#finish(value); + } + + reject(error: V | TimeoutError): void { + if (this.#isRejected || this.#isResolved) { + return; + } + this.#isRejected = true; + this.#finish(error); + } + + resolved(): boolean { + return this.#isResolved; + } + + finished(): boolean { + return this.#isResolved || this.#isRejected; + } + + value(): T | V | TimeoutError | undefined { + return this.#value; + } + + #promise: Promise | undefined; + valueOrThrow(): Promise { + if (!this.#promise) { + this.#promise = (async () => { + await this.#taskPromise; + if (this.#isRejected) { + throw this.#value; + } + return this.#value as T; + })(); + } + return this.#promise; + } }