mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-03 05:55:03 +01:00
feat(utils): scheduleAction reject action gracefully when aborted
This commit is contained in:
committed by
Marek Polák
parent
1735721be4
commit
55ba457ac4
@@ -14,6 +14,7 @@ export type ScheduleActionParams = {
|
||||
| number // How many attempts before failure (default = one, or infinite when deadline is set)
|
||||
| readonly AttemptParams[]; // Array of timeouts and gaps for every attempt (length = attempt count)
|
||||
signal?: AbortSignal;
|
||||
graceful?: boolean; // Abort signalling will not throw immediately but let the action handle it instead (default = false)
|
||||
attemptFailureHandler?: (error: Error) => Error | void; // break attemptLoop if `Error` is set
|
||||
} & AttemptParams; // Ignored when attempts is AttemptParams[]
|
||||
|
||||
@@ -153,13 +154,28 @@ export const scheduleAction = async <T>(
|
||||
const getParams = isArray(attempts)
|
||||
? (attempt: number) => attempts[attempt]
|
||||
: () => ({ timeout, gap });
|
||||
|
||||
const errorDeadline = new ScheduleActionDeadlineError();
|
||||
const errorTimeout = new ScheduleActionTimeoutError();
|
||||
|
||||
const graceful = params.graceful && signal;
|
||||
const actionAborter = new AbortController();
|
||||
if (graceful) {
|
||||
if (signal.aborted) {
|
||||
actionAborter.abort();
|
||||
} else {
|
||||
const onAbort = () => {
|
||||
signal.removeEventListener('abort', onAbort);
|
||||
clear.removeEventListener('abort', onAbort);
|
||||
actionAborter.abort();
|
||||
};
|
||||
signal.addEventListener('abort', onAbort);
|
||||
clear.addEventListener('abort', onAbort);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await Promise.race([
|
||||
rejectWhenAborted(signal, clear),
|
||||
...(graceful ? [] : [rejectWhenAborted(signal, clear)]),
|
||||
...maybeRejectAfterMs(deadlineMs, errorDeadline, clear),
|
||||
resolveAfterMs(delay, clear).then(() =>
|
||||
attemptLoop(
|
||||
@@ -176,7 +192,7 @@ export const scheduleAction = async <T>(
|
||||
? Promise.reject(errorHandlerResult)
|
||||
: resolveAfterMs(getParams(attempt).gap ?? 0, clear);
|
||||
},
|
||||
clear,
|
||||
graceful ? actionAborter.signal : clear,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -239,6 +239,34 @@ describe('scheduleAction', () => {
|
||||
expect(checkListeners()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("don't reject after abort (graceful: true)", async () => {
|
||||
let ctrl = new AbortController();
|
||||
const action = (sig?: AbortSignal) =>
|
||||
new Promise(resolve => {
|
||||
sig?.addEventListener('abort', () => {
|
||||
setTimeout(() => resolve({ success: false }), 1000);
|
||||
});
|
||||
});
|
||||
|
||||
let resultPromise = scheduleAction(action, {
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
new Promise(resolve => setTimeout(resolve, 1));
|
||||
ctrl.abort();
|
||||
await expect(() => resultPromise).rejects.toThrow(ERR_SIGNAL);
|
||||
|
||||
ctrl = new AbortController();
|
||||
resultPromise = scheduleAction(action, {
|
||||
signal: ctrl.signal,
|
||||
graceful: true,
|
||||
timeout: 2000,
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
ctrl.abort();
|
||||
const result = await resultPromise;
|
||||
expect(result).toEqual({ success: false });
|
||||
});
|
||||
|
||||
it('variable timeouts', async () => {
|
||||
const TIMEOUTS = [50, 150, 100];
|
||||
const MARGIN = 10;
|
||||
|
||||
Reference in New Issue
Block a user