mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(spf): memoize Task.run via a named #execute, fixing orphan rejections
Memoize the run-machinery promise itself (`#promise ??= this.#execute()`) rather than reassigning `#promise` to fresh `Promise.resolve(value)` / `Promise.reject(error)` on settle. The reassigned promises were never awaited, so an errored or aborted task that's not re-run (the norm — the RecurringRunner moves on to a clone) left an unhandled rejection. The memoized promise is the one callers await, so it's always handled. Also closes a sync-throw gap: a `#runFn` that throws synchronously is now captured as a rejected memoized promise instead of leaving `#promise` unset (which would re-execute on the next run()). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
557d8bd72f
commit
73f0ef711a
@@ -105,24 +105,28 @@ export class Task<TValue = void, TError = unknown> implements TaskLike<TValue, T
|
||||
}
|
||||
|
||||
run(): Promise<TValue> {
|
||||
// Memoized: run the work once; repeated calls share the same promise (which
|
||||
// resolves/rejects immediately once settled). Re-running needs a `clone()`.
|
||||
this.#promise ??= (async () => {
|
||||
this.#status = 'running';
|
||||
try {
|
||||
const result = await this.#runFn(this.#signal);
|
||||
this.#value = result; // value before status — ordering guarantee
|
||||
this.#status = 'done';
|
||||
return result;
|
||||
} catch (e) {
|
||||
this.#error = e as TError; // error before status — ordering guarantee
|
||||
this.#status = 'error';
|
||||
throw e;
|
||||
}
|
||||
})();
|
||||
// Memoized: run the work once, sharing the same promise across calls. The
|
||||
// memoized promise IS what callers await, so it's always handled (no orphan
|
||||
// `Promise.resolve/reject`); a sync-throwing `#runFn` is captured as a
|
||||
// rejection rather than re-run.
|
||||
this.#promise ??= this.#execute();
|
||||
return this.#promise;
|
||||
}
|
||||
|
||||
async #execute(): Promise<TValue> {
|
||||
this.#status = 'running';
|
||||
try {
|
||||
const result = await this.#runFn(this.#signal);
|
||||
this.#value = result; // value before status — ordering guarantee
|
||||
this.#status = 'done';
|
||||
return result;
|
||||
} catch (e) {
|
||||
this.#error = e as TError; // error before status — ordering guarantee
|
||||
this.#status = 'error';
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
abort(): void {
|
||||
this.#abortController.abort();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user