Gathering detailed insights and metrics for abort-controller-x
Gathering detailed insights and metrics for abort-controller-x
Gathering detailed insights and metrics for abort-controller-x
Gathering detailed insights and metrics for abort-controller-x
Abortable async function primitives and combinators
npm install abort-controller-x
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
21 Stars
57 Commits
5 Forks
6 Watching
1 Branches
11 Contributors
Updated on 23 Apr 2024
TypeScript (99.67%)
JavaScript (0.33%)
Cumulative downloads
Total Downloads
Last day
-12.9%
23,390
Compared to previous day
Last week
-1.9%
137,852
Compared to previous week
Last month
6.6%
594,663
Compared to previous month
Last year
14.5%
5,184,619
Compared to previous year
Abortable async function primitives and combinators.
yarn add abort-controller-x
See
AbortController
MDN page.
AbortController is
available in NodeJS
since 15.0.0, NodeJS 14.17+ requires the
--experimental-abortcontroller
flag. A polyfill is available
for older NodeJS versions and browsers.
We define abortable function as a function that obeys following rules:
AbortSignal
in its arguments.Promise
.abort
event listener to the AbortSignal
. Once the AbortSignal
is aborted, the
returned Promise
must reject with AbortError
either immediately, or after
doing any async cleanup. It's also possible to reject with other errors that
happen during cleanup.Promise
is fulfilled or rejected, it must remove
abort
event listener.An example of abortable function is the standard
fetch
function.
This library provides a way to build complex abortable functions using standard
async
/await
syntax, without the burden of manually managing
abort
event listeners. You can reuse a single AbortSignal
between many operations
inside a parent function:
1/** 2 * Make requests repeatedly with a delay between consecutive requests 3 */ 4async function makeRequests(signal: AbortSignal): Promise<never> { 5 while (true) { 6 await fetch('...', {signal}); 7 await delay(signal, 1000); 8 } 9} 10 11const abortController = new AbortController(); 12 13makeRequests(abortController.signal).catch(catchAbortError); 14 15process.on('SIGTERM', () => { 16 abortController.abort(); 17});
The above example can be rewritten in a more ergonomic way using run
helper.
Usually you should only create AbortController
somewhere on the top level, and
in regular code use async
/await
and pass AbortSignal
to abortable
functions provided by this library or custom ones composed of other abortable
functions.
abort-controller-x-rxjs
— Abortable helpers for RxJS.abort-controller-x-reactive-store
— Reactive store primitive and helpers.all
1function all<T>( 2 signal: AbortSignal, 3 executor: (innerSignal: AbortSignal) => readonly PromiseLike<T>[], 4): Promise<T[]>;
Abortable version of Promise.all
.
Creates new inner AbortSignal
and passes it to executor
. That signal is
aborted when signal
is aborted or any of the promises returned from executor
are rejected.
Returns a promise that fulfills with an array of results when all of the
promises returned from executor
fulfill, rejects when any of the promises
returned from executor
are rejected, and rejects with AbortError
when
signal
is aborted.
The promises returned from executor
must be abortable, i.e. once innerSignal
is aborted, they must reject with AbortError
either immediately, or after
doing any async cleanup.
Example:
1const [result1, result2] = await all(signal, signal => [ 2 makeRequest(signal, params1), 3 makeRequest(signal, params2), 4]);
race
1function race<T>( 2 signal: AbortSignal, 3 executor: (innerSignal: AbortSignal) => readonly PromiseLike<T>[], 4): Promise<T>;
Abortable version of Promise.race
.
Creates new inner AbortSignal
and passes it to executor
. That signal is
aborted when signal
is aborted or any of the promises returned from executor
are fulfilled or rejected.
Returns a promise that fulfills or rejects when any of the promises returned
from executor
are fulfilled or rejected, and rejects with AbortError
when
signal
is aborted.
The promises returned from executor
must be abortable, i.e. once innerSignal
is aborted, they must reject with AbortError
either immediately, or after
doing any async cleanup.
Example:
1const result = await race(signal, signal => [ 2 delay(signal, 1000).then(() => ({status: 'timeout'})), 3 makeRequest(signal, params).then(value => ({status: 'success', value})), 4]); 5 6if (result.status === 'timeout') { 7 // request timed out 8} else { 9 const response = result.value; 10}
delay
1function delay(signal: AbortSignal, dueTime: number | Date): Promise<void>;
Return a promise that resolves after delay and rejects with AbortError
once
signal
is aborted.
The delay time is specified as a Date
object or as an integer denoting
milliseconds to wait.
Example:
1// Make a request repeatedly with a delay between consecutive requests 2while (true) { 3 await makeRequest(signal, params); 4 await delay(signal, 1000); 5}
Example:
1// Make a request repeatedly with a fixed interval 2import {addMilliseconds} from 'date-fns'; 3 4let date = new Date(); 5 6while (true) { 7 await makeRequest(signal, params); 8 9 date = addMilliseconds(date, 1000); 10 await delay(signal, date); 11}
waitForEvent
1function waitForEvent<T>( 2 signal: AbortSignal, 3 target: EventTargetLike<T>, 4 eventName: string, 5 options?: EventListenerOptions, 6): Promise<T>;
Returns a promise that fulfills when an event of specific type is emitted from
given event target and rejects with AbortError
once signal
is aborted.
Example:
1// Create a WebSocket and wait for connection 2const webSocket = new WebSocket(url); 3 4const openEvent = await race(signal, signal => [ 5 waitForEvent<WebSocketEventMap['open']>(signal, webSocket, 'open'), 6 waitForEvent<WebSocketEventMap['close']>(signal, webSocket, 'close').then( 7 event => { 8 throw new Error(`Failed to connect to ${url}: ${event.reason}`); 9 }, 10 ), 11]);
forever
1function forever(signal: AbortSignal): Promise<never>;
Return a promise that never fulfills and only rejects with AbortError
once
signal
is aborted.
spawn
1function spawn<T>( 2 signal: AbortSignal, 3 fn: (signal: AbortSignal, effects: SpawnEffects) => Promise<T>, 4): Promise<T>; 5 6type SpawnEffects = { 7 defer(fn: () => void | Promise<void>): void; 8 fork<T>(fn: (signal: AbortSignal) => Promise<T>): ForkTask<T>; 9}; 10 11type ForkTask<T> = { 12 abort(): void; 13 join(): Promise<T>; 14};
Run an abortable function with fork
and defer
effects attached to it.
spawn
allows to write Go-style coroutines.
SpawnEffects.defer
Schedules a function to run after spawned function finishes.
Deferred functions run serially in last-in-first-out order.
Promise returned from spawn
resolves or rejects only after all deferred
functions finish.
SpawnEffects.fork
Executes an abortable function in background.
If a forked function throws an exception, spawned function and other forks are
aborted and promise returned from spawn
rejects with that exception.
When spawned function finishes, all forks are aborted.
ForkTask.abort
Abort a forked function.
ForkTask.join
Returns a promise returned from a forked function.
Example:
1// Connect to a database, then start a server, then block until abort. 2// On abort, gracefully shutdown the server, and once done, disconnect 3// from the database. 4spawn(signal, async (signal, {defer}) => { 5 const db = await connectToDb(); 6 7 defer(async () => { 8 await db.close(); 9 }); 10 11 const server = await startServer(db); 12 13 defer(async () => { 14 await server.close(); 15 }); 16 17 await forever(signal); 18});
Example:
1// Connect to a database, then start an infinite polling loop. 2// On abort, disconnect from the database. 3spawn(signal, async (signal, {defer}) => { 4 const db = await connectToDb(); 5 6 defer(async () => { 7 await db.close(); 8 }); 9 10 while (true) { 11 await poll(signal, db); 12 await delay(signal, 5000); 13 } 14});
Example:
1// Acquire a lock and execute a function. 2// Extend the lock while the function is running. 3// Once the function finishes or the signal is aborted, stop extending 4// the lock and release it. 5import Redlock = require('redlock'); 6 7const lockTtl = 30_000; 8 9function withLock<T>( 10 signal: AbortSignal, 11 redlock: Redlock, 12 key: string, 13 fn: (signal: AbortSignal) => Promise<T>, 14): Promise<T> { 15 return spawn(signal, async (signal, {fork, defer}) => { 16 const lock = await redlock.lock(key, lockTtl); 17 18 defer(() => lock.unlock()); 19 20 fork(async signal => { 21 while (true) { 22 await delay(signal, lockTtl / 10); 23 await lock.extend(lockTtl); 24 } 25 }); 26 27 return await fn(signal); 28 }); 29} 30 31const redlock = new Redlock([redis], { 32 retryCount: -1, 33}); 34 35await withLock(signal, redlock, 'the-lock-key', async signal => { 36 // ... 37});
retry
1function retry<T>( 2 signal: AbortSignal, 3 fn: (signal: AbortSignal, attempt: number, reset: () => void) => Promise<T>, 4 options?: RetryOptions, 5): Promise<T>; 6 7type RetryOptions = { 8 baseMs?: number; 9 maxDelayMs?: number; 10 maxAttempts?: number; 11 onError?: (error: unknown, attempt: number, delayMs: number) => void; 12};
Retry a function with exponential backoff.
fn
A function that will be called and retried in case of error. It receives:
signal
AbortSignal
that is aborted when the signal passed to retry
is aborted.
attempt
Attempt number starting with 0.
reset
Function that sets attempt number to -1 so that the next attempt will be made without delay.
RetryOptions.baseMs
Starting delay before first retry attempt in milliseconds.
Defaults to 1000.
Example: if baseMs
is 100, then retries will be attempted in 100ms, 200ms,
400ms etc (not counting jitter).
RetryOptions.maxDelayMs
Maximum delay between attempts in milliseconds.
Defaults to 30 seconds.
Example: if baseMs
is 1000 and maxDelayMs
is 3000, then retries will be
attempted in 1000ms, 2000ms, 3000ms, 3000ms etc (not counting jitter).
RetryOptions.maxAttempts
Maximum for the total number of attempts.
Defaults to Infinity
.
RetryOptions.onError
Called after each failed attempt before setting delay timer.
Rethrow error from this callback to prevent further retries.
proactiveRetry
1function proactiveRetry<T>( 2 signal: AbortSignal, 3 fn: (signal: AbortSignal, attempt: number) => Promise<T>, 4 options?: ProactiveRetryOptions, 5): Promise<T>; 6 7type ProactiveRetryOptions = { 8 baseMs?: number; 9 maxAttempts?: number; 10 onError?: (error: unknown, attempt: number) => void; 11};
Proactively retry a function with exponential backoff.
Also known as hedging.
The function will be called multiple times in parallel until it succeeds, in which case all the other calls will be aborted.
fn
A function that will be called multiple times in parallel until it succeeds. It receives:
signal
AbortSignal
that is aborted when the signal passed to retry
is aborted,
or when the function succeeds.
attempt
Attempt number starting with 0.
ProactiveRetryOptions.baseMs
Base delay between attempts in milliseconds.
Defaults to 1000.
Example: if baseMs
is 100, then retries will be attempted in 100ms, 200ms,
400ms etc (not counting jitter).
ProactiveRetryOptions.maxAttempts
Maximum for the total number of attempts.
Defaults to Infinity
.
ProactiveRetryOptions.onError
Called after each failed attempt.
Rethrow error from this callback to prevent further retries.
execute
1function execute<T>( 2 signal: AbortSignal, 3 executor: ( 4 resolve: (value: T) => void, 5 reject: (reason?: any) => void, 6 ) => () => void | PromiseLike<void>, 7): Promise<T>;
Similar to new Promise(executor)
, but allows executor to return abort callback
that is called once signal
is aborted.
Returned promise rejects with AbortError
once signal
is aborted.
Callback can return a promise, e.g. for doing any async cleanup. In this case,
the promise returned from execute
rejects with AbortError
after that promise
fulfills.
abortable
1function abortable<T>(signal: AbortSignal, promise: PromiseLike<T>): Promise<T>;
Wrap a promise to reject with AbortError
once signal
is aborted.
Useful to wrap non-abortable promises. Note that underlying process will NOT be aborted.
run
1function run(fn: (signal: AbortSignal) => Promise<void>): () => Promise<void>;
Invokes an abortable function with implicitly created AbortSignal
.
Returns a function that aborts that signal and waits until passed function finishes.
Any error other than AbortError
thrown from passed function will result in
unhandled promise rejection.
Example:
1const stop = run(async signal => { 2 try { 3 while (true) { 4 await delay(signal, 1000); 5 console.log('tick'); 6 } 7 } finally { 8 await doCleanup(); 9 } 10}); 11 12// abort and wait until cleanup is done 13await stop();
This function is also useful with React useEffect
hook:
1// make requests periodically while the component is mounted 2useEffect( 3 () => 4 run(async signal => { 5 while (true) { 6 await makeRequest(signal); 7 await delay(signal, 1000); 8 } 9 }), 10 [], 11);
AbortError
1class AbortError extends Error
Thrown when an abortable function was aborted.
Warning: do not use instanceof
with this class. Instead, use
isAbortError
function.
isAbortError
1function isAbortError(error: unknown): boolean;
Checks whether given error
is an AbortError
.
throwIfAborted
1function throwIfAborted(signal: AbortSignal): void;
If signal
is aborted, throws AbortError
. Otherwise does nothing.
rethrowAbortError
1function rethrowAbortError(error: unknown): void;
If error
is AbortError
, throws it. Otherwise does nothing.
Useful for try/catch
blocks around abortable code:
1try {
2 await somethingAbortable(signal);
3} catch (err) {
4 rethrowAbortError(err);
5
6 // do normal error handling
7}
catchAbortError
1function catchAbortError(error: unknown): void;
If error
is AbortError
, does nothing. Otherwise throws it.
Useful for invoking top-level abortable functions:
1somethingAbortable(signal).catch(catchAbortError);
Without catchAbortError
, aborting would result in unhandled promise rejection.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 4/30 approved changesets -- score normalized to 1
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
13 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More