@jsenv/abort
Advanced tools
Comparing version 0.0.1 to 1.0.0
export { Abort } from "./src/abort.js" | ||
export { createCallbackList } from "./src/callback_list.js" | ||
export { raceCallbacks } from "./src/callback_race.js" |
export { Abort } from "./src/abort.js" | ||
export { createCallbackList } from "./src/callback_list.js" | ||
export { raceCallbacks } from "./src/callback_race.js" | ||
// exports specific to Node.js | ||
export { raceProcessTeardownEvents } from "./src/process_teardown_events.js" |
{ | ||
"name": "@jsenv/abort", | ||
"version": "0.0.1", | ||
"version": "1.0.0", | ||
"description": "Help to write code compatible with abort signals", | ||
@@ -19,4 +19,3 @@ "license": "MIT", | ||
"publishConfig": { | ||
"access": "public", | ||
"registry": "https://registry.npmjs.org" | ||
"access": "public" | ||
}, | ||
@@ -23,0 +22,0 @@ "type": "module", |
@@ -5,10 +5,9 @@ # Abort [![npm package](https://img.shields.io/npm/v/@jsenv/abort.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/abort) [![github main](https://github.com/jsenv/abort/workflows/main/badge.svg)](https://github.com/jsenv/abort/actions?workflow=main) [![codecov coverage](https://codecov.io/gh/jsenv/abort/branch/main/graph/badge.svg)](https://codecov.io/gh/jsenv/abort) | ||
- API designed to properly cleanup things (event listeners, timeouts, ...) | ||
- Prevents memory leak | ||
- No maxListeners warning for `"abort"` event on Node.js | ||
- API cleanup things by default | ||
- Removing event listeners, clearing timeouts, ... | ||
- Allow to safely bypass max listeners warning when needed | ||
- Code can do 20 abortable fetch requests in parallel without warnings | ||
- Manage multiple abort signals | ||
- Compose abort signals from multiple sources | ||
- Code can react differently according to which signal aborted the operation | ||
- Allow to bypass safely max listeners warning when a single signal NEEDS to be listened lot of times | ||
- No "potential memory leak" when code does 20 http requests in parallel | ||
- Code can do differents things depending on which signal aborted the operation | ||
@@ -30,7 +29,11 @@ # Example | ||
The code above forwards an abort signal to `customFetch`. Code below shows how `"@jsenv/abort"` helps to manage the received signal. | ||
Code above pass an abort signal to `customFetch`. | ||
Let's see how `"@jsenv/abort"` helps to manage the signal received. | ||
_fetch_custom.mjs_ | ||
```js | ||
import { Abort } from "@jsenv/abort" | ||
export const customFetch = ( | ||
@@ -58,15 +61,19 @@ url, | ||
try { | ||
const response = await fetch(url, { signal }) | ||
const response = await fetch(url, { signal: fetchOperation.signal }) | ||
return response | ||
} catch (e) { | ||
if (SIGINTAbortSource.signal.aborted) { | ||
// aborted by SIGINT | ||
if (Abort.isAbortError(e)) { | ||
if (SIGINTAbortSource.signal.aborted) { | ||
// aborted by SIGINT | ||
} | ||
if (timeoutAbortSource.signal.aborted) { | ||
// aborted by timeout | ||
} | ||
if (signal.aborted) { | ||
// aborted from outside | ||
} | ||
} | ||
if (timeoutAbortSource.signal.aborted) { | ||
// aborted by timeout | ||
} | ||
if (signal.aborted) { | ||
// aborted from outside | ||
} | ||
throw e | ||
} finally { | ||
await fetchOperation.clean() | ||
} | ||
@@ -73,0 +80,0 @@ } |
@@ -16,4 +16,2 @@ /* | ||
}, | ||
// maybe reintroduce fromSignal? | ||
} | ||
@@ -37,3 +35,3 @@ | ||
callbacks.forEach((callback) => { | ||
callback("operation aborted") | ||
callback(FROM_ABORT) | ||
}) | ||
@@ -55,3 +53,10 @@ } | ||
const gracefulStop = async (reason) => { | ||
const clean = async () => { | ||
// do not trigger abortCallbackList, we'll do it | ||
operationSignal.onabort = null | ||
// but do call any remaining "abort" callback still registered on operation signal | ||
// This ensure they are properly executed and do not stay | ||
// here listening forever (especially when registered with {once: true}) | ||
operationAbortController.abort() | ||
const callbacks = abortCallbackList.copy() | ||
@@ -61,3 +66,3 @@ abortCallbackList.clear() | ||
callbacks.map(async (callback) => { | ||
await callback(reason) | ||
await callback(FROM_CLEAN) | ||
}), | ||
@@ -69,6 +74,3 @@ ) | ||
if (operationSignal.aborted) { | ||
return { | ||
signal, | ||
cancel, | ||
} | ||
return cancel | ||
} | ||
@@ -78,6 +80,3 @@ | ||
operationAbortController.abort() | ||
return { | ||
signal, | ||
cancel, | ||
} | ||
return cancel | ||
} | ||
@@ -111,8 +110,5 @@ | ||
return { | ||
signal, | ||
cancel: () => { | ||
cancelRace() | ||
cancel() | ||
}, | ||
return () => { | ||
cancelRace() | ||
cancel() | ||
} | ||
@@ -131,3 +127,7 @@ } | ||
return addAbortSignal(abortSourceSignal, cancelEffect) | ||
const removeAbortSignal = addAbortSignal(abortSourceSignal, cancelEffect) | ||
return { | ||
signal: abortSourceSignal, | ||
remove: removeAbortSignal, | ||
} | ||
} | ||
@@ -146,10 +146,7 @@ | ||
throwIfAborted() | ||
const abortController = new AbortController() | ||
const signal = abortController.signal | ||
const removeAbortCallback = abortCallbackList.add(() => { | ||
abortController.abort() | ||
}) | ||
try { | ||
@@ -165,6 +162,23 @@ const value = await asyncCallback(signal) | ||
const withSignalSync = (callback) => { | ||
const abortController = new AbortController() | ||
const signal = abortController.signal | ||
const removeAbortCallback = abortCallbackList.add(() => { | ||
abortController.abort() | ||
}) | ||
try { | ||
const value = callback(signal) | ||
removeAbortCallback() | ||
return value | ||
} catch (e) { | ||
removeAbortCallback() | ||
throw e | ||
} | ||
} | ||
return { | ||
// We don't have to expose the operationSignal | ||
// it can be used to know if the operation is aborted so it mabe be handy | ||
// but in that case we should rather prefer .aborted directly? | ||
// We could almost hide the operationSignal | ||
// But it can be handy for 2 things: | ||
// - know if operation is aborted (operation.signal.aborted) | ||
// - forward the operation.signal directly (not using "withSignal" or "withSignalSync") | ||
signal: operationSignal, | ||
@@ -178,6 +192,10 @@ | ||
withSignal, | ||
gracefulStop, | ||
withSignalSync, | ||
clean, | ||
} | ||
} | ||
const FROM_ABORT = { from: "abort" } | ||
const FROM_CLEAN = { from: "clean" } | ||
const cancelNoop = () => {} | ||
@@ -184,0 +202,0 @@ |
@@ -17,3 +17,3 @@ export const createCallbackList = () => { | ||
CODE: "CALLBACK_DUPLICATION", | ||
detail: `It's often the sign that code is executd more than once`, | ||
detail: `Code is potentially executed more than it should`, | ||
}) | ||
@@ -20,0 +20,0 @@ } else { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
15526
319
1
85