refactor: make Deferred.valueOrThrow idempotent. (#11657)

This commit is contained in:
jrandolf 2024-01-09 11:40:26 +01:00 committed by GitHub
parent 6990571d25
commit 93fb2f4ea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -18,70 +18,9 @@ export interface DeferredOptions {
* @internal
*/
export class Deferred<T, V extends Error = Error> {
#isResolved = false;
#isRejected = false;
#value: T | V | TimeoutError | undefined;
#resolver: (value: void) => void = () => {};
#taskPromise = new Promise<void>(resolve => {
this.#resolver = resolve;
});
#timeoutId: ReturnType<typeof setTimeout> | 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<T> {
await this.#taskPromise;
if (this.#isRejected) {
throw this.#value;
}
return this.#value as T;
}
static create<R, X extends Error = Error>(
opts?: DeferredOptions
): Deferred<R> {
): Deferred<R, X> {
return new Deferred<R, X>(opts);
}
@ -112,4 +51,72 @@ export class Deferred<T, V extends Error = Error> {
}
}
}
#isResolved = false;
#isRejected = false;
#value: T | V | TimeoutError | undefined;
// SAFETY: This is ensured by #taskPromise.
#resolve!: (value: void) => void;
#taskPromise = new Promise<void>(resolve => {
this.#resolve = resolve;
});
#timeoutId: ReturnType<typeof setTimeout> | 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<T> | undefined;
valueOrThrow(): Promise<T> {
if (!this.#promise) {
this.#promise = (async () => {
await this.#taskPromise;
if (this.#isRejected) {
throw this.#value;
}
return this.#value as T;
})();
}
return this.#promise;
}
}