deno.land / x / cockatiel@v3.1.2 / src / TimeoutPolicy.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117import { deriveAbortController } from './common/abort';import { Event, EventEmitter, onAbort } from './common/Event';import { ExecuteWrapper, returnOrThrow } from './common/Executor';import { TaskCancelledError } from './errors/TaskCancelledError';import { IPolicy } from './Policy';
export enum TimeoutStrategy { /** * Cooperative timeouts will simply revoke the inner cancellation token, * assuming the caller handles cancellation and throws or returns appropriately. */ Cooperative = 'optimistic',
/** * Aggressive cancellation immediately throws */ Aggressive = 'aggressive',}
export interface ICancellationContext { signal: AbortSignal;}
export interface ITimeoutOptions { /** Strategy for timeouts, "Cooperative", or "Accessive" */ strategy: TimeoutStrategy; /** * Whether the AbortSignal should be aborted when the * function returns. Defaults to true. */ abortOnReturn?: boolean;}
export class TimeoutPolicy implements IPolicy<ICancellationContext> { declare readonly _altReturn: never;
private readonly timeoutEmitter = new EventEmitter<void>();
/** * @inheritdoc */ public readonly onTimeout = this.timeoutEmitter.addListener;
/** * @inheritdoc */ public readonly onFailure = this.executor.onFailure;
/** * @inheritdoc */ public readonly onSuccess = this.executor.onSuccess;
constructor( private readonly duration: number, private readonly options: ITimeoutOptions, private readonly executor = new ExecuteWrapper(), private readonly unref = false, ) {}
/** * When timing out, a referenced timer is created. This means the Node.js * event loop is kept active while we're waiting for the timeout, as long as * the function hasn't returned. Calling this method on the timeout builder * will unreference the timer, allowing the process to exit even if a * timeout might still be happening. */ public dangerouslyUnref() { const t = new TimeoutPolicy(this.duration, this.options, this.executor, true); return t; }
/** * Executes the given function. * @param fn Function to execute. Takes in a nested cancellation token. * @throws a {@link TaskCancelledError} if a timeout occurs */ public async execute<T>( fn: (context: ICancellationContext, signal: AbortSignal) => PromiseLike<T> | T, signal?: AbortSignal, ): Promise<T> { const aborter = deriveAbortController(signal); const timer = setTimeout(() => aborter.abort(), this.duration); if (this.unref) { timer.unref(); }
const context = { signal: aborter.signal };
const onceAborted = onAbort(aborter.signal); const onCancelledListener = onceAborted(() => this.timeoutEmitter.emit());
try { if (this.options.strategy === TimeoutStrategy.Cooperative) { return returnOrThrow(await this.executor.invoke(fn, context, aborter.signal)); }
return await this.executor .invoke(async () => Promise.race<T>([ Promise.resolve(fn(context, aborter.signal)), Event.toPromise(onceAborted).then(() => { throw new TaskCancelledError(`Operation timed out after ${this.duration}ms`); }), ]), ) .then(returnOrThrow); } finally { onCancelledListener.dispose(); if (this.options.abortOnReturn !== false) { aborter.abort(); } clearTimeout(timer); } }}
Version Info