isotropic-timeout 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
File without changes
package/README.md CHANGED
@@ -5,16 +5,17 @@
5
5
  ![](https://img.shields.io/badge/tests-passing-brightgreen.svg)
6
6
  ![](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)
7
7
 
8
- A utility that limits the amount of time a callback function will wait to be called or a promise will wait to be resolved/rejected.
8
+ A utility that limits the amount of time a callback function will wait to be called or a promise will wait to settle.
9
9
 
10
10
  ## Why Use This?
11
11
 
12
12
  - **Prevent Indefinite Waiting**: Set maximum wait times for asynchronous operations
13
13
  - **Dual API Support**: Works with both callbacks and promises
14
- - **Error Handling**: Provides standardized timeout errors
15
- - **Late Response Handling**: Option to handle responses that arrive after timeout
16
- - **Simplified Syntax**: Clean, concise API for common timeout patterns
17
- - **Zero Dependencies**: Lightweight implementation with minimal external requirements
14
+ - **Cancelable**: The returned function or promise has a `cancel` method supporting reasons, abort signals, and silent cancellation
15
+ - **Temporal Support**: The timeout accepts a number of milliseconds or a `Temporal.Duration`
16
+ - **Standardized Errors**: Generates the same `TimeoutError`, `AbortError`, and `edError` objects used across the isotropic ecosystem
17
+ - **Late Response Handling**: Option to handle responses that arrive after cancellation
18
+ - **Minimal Dependencies**: A convenience wrapper around `isotropic-timeout-cancel`
18
19
 
19
20
  ## Installation
20
21
 
@@ -58,9 +59,9 @@ import _timeout from 'isotropic-timeout';
58
59
  import _timeout from 'isotropic-timeout';
59
60
 
60
61
  // Wrap a promise with a timeout
61
- const _fetchWithTimeout = async url {
62
+ const _fetchWithTimeout = async url => {
62
63
  try {
63
- const response = await timeout(
64
+ const response = await _timeout(
64
65
  5000, // Timeout in milliseconds
65
66
  fetch(url), // Promise to wrap
66
67
  (error, result) => { // Optional late callback
@@ -83,22 +84,34 @@ const _fetchWithTimeout = async url {
83
84
 
84
85
  ## API
85
86
 
86
- ### timeout(milliseconds, callbackFunctionOrPromise, lateCallbackFunction)
87
+ ### timeout(timeout, callbackFunctionOrPromise, lateCallbackFunction)
87
88
 
88
- A function that applies a timeout to a callback function or promise.
89
+ A function that applies a time limit to a callback function or promise.
89
90
 
90
91
  #### Parameters
91
92
 
92
- - `milliseconds` (Number): The timeout duration in milliseconds
93
+ - `timeout` (Number | Temporal.Duration | Object): The time limit, or a config object. A number is interpreted as milliseconds. A config object accepts:
94
+ - `timeout` (Number | Temporal.Duration, optional): The time limit. When omitted or not finite, the operation only ends by completion or cancellation.
95
+ - `details` (Object, optional): Included as the `details` of generated errors
96
+ - `signal` (AbortSignal, optional): An abort signal that cancels the operation
97
+ - `subject` (String, optional): The subject of generated error messages. Default: `'Operation'`
93
98
  - `callbackFunctionOrPromise` (Function|Promise):
94
99
  - If a function: The callback to be wrapped with timeout functionality
95
100
  - If a promise: The promise to be wrapped with timeout functionality
96
- - `lateCallbackFunction` (Function, optional): Function to be called if the original operation completes after timeout
101
+ - `lateCallbackFunction` (Function, optional): Function to be called if the original operation completes after cancellation (whether by timeout, signal, or a `cancel` call). Its signature depends on the mode:
102
+ - Callback mode: invoked with the same `this` context and arguments that the wrapped function was called with (mirroring the original callback).
103
+ - Promise mode: invoked as a Node-style errback, `(null, value)` if the promise resolved, or `(error)` if it rejected.
97
104
 
98
105
  #### Returns
99
106
 
100
- - If `callbackFunctionOrPromise` is a function: Returns a wrapped function that will either call the original callback or trigger a timeout error
101
- - If `callbackFunctionOrPromise` is a promise: Returns a new promise that will either resolve/reject with the original promise or reject with a timeout error
107
+ - If `callbackFunctionOrPromise` is a function: Returns a wrapped function that will either call the original callback or deliver a cancellation error
108
+ - If `callbackFunctionOrPromise` is a promise: Returns a new promise that will either settle with the original promise or reject with a cancellation error
109
+
110
+ Either return value has a `cancel(config)` method accepting the same `reason`, `signal`, and `silent` options as [isotropic-cancel](https://github.com/ibi-group/isotropic-cancel), returning the wrapped function or promise for chaining. Canceling delivers the error to the main callback or rejects the promise (unless `silent`), and subsequent completions route to the `lateCallbackFunction`.
111
+
112
+ #### Errors
113
+
114
+ Generated errors follow the isotropic conventions: `TimeoutError` with message `` `${subject} timed out` `` and `details` including `duration`, `AbortError` when a signal aborts, and `CanceledError` when `cancel` is called without a reason. See [isotropic-timeout-cancel](https://github.com/ibi-group/isotropic-timeout-cancel) for details.
102
115
 
103
116
  ## Examples
104
117
 
@@ -200,8 +213,8 @@ const _queryWithTimeout = (db, query, params, timeout = 5000) => {
200
213
 
201
214
  When a timeout occurs, the utility generates a standardized `TimeoutError` (an instance of `isotropic-error`) with:
202
215
 
203
- - `details`: Object containing the timeout duration
204
- - `message`: 'Timeout after X milliseconds'
216
+ - `details`: Object containing the timeout duration in `duration`, merged with any configured `details`
217
+ - `message`: 'Task timed out' (the subject is configurable)
205
218
  - `name`: 'TimeoutError'
206
219
 
207
220
  This makes error handling and identification consistent across your application.
package/lib/timeout.js CHANGED
@@ -1,2 +1,93 @@
1
- import _Error from"isotropic-error";import _later from"isotropic-later";export default((a,b,c)=>{if("function"==typeof b){const d=_later(a,()=>{b(_Error({details:{milliseconds:a},message:`Timeout after ${a} milliseconds`,name:"TimeoutError"}))});return function(...a){return d.completed?c?Reflect.apply(c,this,a):void 0:(d.cancel(),Reflect.apply(b,this,a))}}return new Promise((d,e)=>{const f=_later(a,()=>{e(_Error({details:{milliseconds:a},message:`Timeout after ${a} milliseconds`,name:"TimeoutError"}))});b.then(a=>{f.completed?c&&c(null,a):(f.cancel(),d(a))},a=>{f.completed?c&&c(a):(f.cancel(),e(a))})})});
2
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfRXJyb3IiLCJfbGF0ZXIiLCJtaWxsaXNlY29uZHMiLCJjYWxsYmFja0Z1bmN0aW9uT3JQcm9taXNlIiwibGF0ZUNhbGxiYWNrRnVuY3Rpb24iLCJoYW5kbGUiLCJkZXRhaWxzIiwibWVzc2FnZSIsIm5hbWUiLCJhcmdzIiwiY29tcGxldGVkIiwiUmVmbGVjdCIsImFwcGx5IiwiY2FuY2VsIiwiUHJvbWlzZSIsInJlc29sdmUiLCJyZWplY3QiLCJ0aGVuIiwidmFsdWUiLCJlcnJvciJdLCJzb3VyY2VzIjpbIi4uL2pzL3RpbWVvdXQuanMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IF9FcnJvciBmcm9tICdpc290cm9waWMtZXJyb3InO1xuaW1wb3J0IF9sYXRlciBmcm9tICdpc290cm9waWMtbGF0ZXInO1xuXG5leHBvcnQgZGVmYXVsdCAobWlsbGlzZWNvbmRzLCBjYWxsYmFja0Z1bmN0aW9uT3JQcm9taXNlLCBsYXRlQ2FsbGJhY2tGdW5jdGlvbikgPT4ge1xuICAgIGlmICh0eXBlb2YgY2FsbGJhY2tGdW5jdGlvbk9yUHJvbWlzZSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBjb25zdCBoYW5kbGUgPSBfbGF0ZXIobWlsbGlzZWNvbmRzLCAoKSA9PiB7XG4gICAgICAgICAgICBjYWxsYmFja0Z1bmN0aW9uT3JQcm9taXNlKF9FcnJvcih7XG4gICAgICAgICAgICAgICAgZGV0YWlsczoge1xuICAgICAgICAgICAgICAgICAgICBtaWxsaXNlY29uZHNcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIG1lc3NhZ2U6IGBUaW1lb3V0IGFmdGVyICR7bWlsbGlzZWNvbmRzfSBtaWxsaXNlY29uZHNgLFxuICAgICAgICAgICAgICAgIG5hbWU6ICdUaW1lb3V0RXJyb3InXG4gICAgICAgICAgICB9KSk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIHJldHVybiBmdW5jdGlvbiAoLi4uYXJncykge1xuICAgICAgICAgICAgaWYgKCFoYW5kbGUuY29tcGxldGVkKSB7XG4gICAgICAgICAgICAgICAgaGFuZGxlLmNhbmNlbCgpO1xuICAgICAgICAgICAgICAgIHJldHVybiBSZWZsZWN0LmFwcGx5KGNhbGxiYWNrRnVuY3Rpb25PclByb21pc2UsIHRoaXMsIGFyZ3MpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAobGF0ZUNhbGxiYWNrRnVuY3Rpb24pIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gUmVmbGVjdC5hcHBseShsYXRlQ2FsbGJhY2tGdW5jdGlvbiwgdGhpcywgYXJncyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgY29uc3QgaGFuZGxlID0gX2xhdGVyKG1pbGxpc2Vjb25kcywgKCkgPT4ge1xuICAgICAgICAgICAgcmVqZWN0KF9FcnJvcih7XG4gICAgICAgICAgICAgICAgZGV0YWlsczoge1xuICAgICAgICAgICAgICAgICAgICBtaWxsaXNlY29uZHNcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIG1lc3NhZ2U6IGBUaW1lb3V0IGFmdGVyICR7bWlsbGlzZWNvbmRzfSBtaWxsaXNlY29uZHNgLFxuICAgICAgICAgICAgICAgIG5hbWU6ICdUaW1lb3V0RXJyb3InXG4gICAgICAgICAgICB9KSk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNhbGxiYWNrRnVuY3Rpb25PclByb21pc2UudGhlbih2YWx1ZSA9PiB7XG4gICAgICAgICAgICBpZiAoIWhhbmRsZS5jb21wbGV0ZWQpIHtcbiAgICAgICAgICAgICAgICBoYW5kbGUuY2FuY2VsKCk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSh2YWx1ZSk7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKGxhdGVDYWxsYmFja0Z1bmN0aW9uKSB7XG4gICAgICAgICAgICAgICAgbGF0ZUNhbGxiYWNrRnVuY3Rpb24obnVsbCwgdmFsdWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LCBlcnJvciA9PiB7XG4gICAgICAgICAgICBpZiAoIWhhbmRsZS5jb21wbGV0ZWQpIHtcbiAgICAgICAgICAgICAgICBoYW5kbGUuY2FuY2VsKCk7XG4gICAgICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAobGF0ZUNhbGxiYWNrRnVuY3Rpb24pIHtcbiAgICAgICAgICAgICAgICBsYXRlQ2FsbGJhY2tGdW5jdGlvbihlcnJvcik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH0pO1xufTtcbiJdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTyxDQUFBQSxNQUFNLEtBQU0saUJBQWlCLENBQ3BDLE1BQU8sQ0FBQUMsTUFBTSxLQUFNLGlCQUFpQixDQUVwQyxlQUFlLENBQUNDLENBQVksQ0FBRUMsQ0FBeUIsQ0FBRUMsQ0FBb0IsR0FBSyxDQUM5RSxHQUF5QyxVQUFVLEVBQS9DLE1BQU8sQ0FBQUQsQ0FBd0MsQ0FBRSxDQUNqRCxLQUFNLENBQUFFLENBQU0sQ0FBR0osTUFBTSxDQUFDQyxDQUFZLENBQUUsSUFBTSxDQUN0Q0MsQ0FBeUIsQ0FBQ0gsTUFBTSxDQUFDLENBQzdCTSxPQUFPLENBQUUsQ0FDTEosWUFBWSxDQUFaQSxDQUNKLENBQUMsQ0FDREssT0FBTyxDQUFFLGlCQUFpQkwsQ0FBWSxlQUFlLENBQ3JETSxJQUFJLENBQUUsY0FDVixDQUFDLENBQUMsQ0FDTixDQUFDLENBQUMsQ0FFRixNQUFPLFVBQVUsR0FBR0MsQ0FBSSxDQUFFLE9BQ2pCLENBQUFKLENBQU0sQ0FBQ0ssU0FBUyxDQUtqQk4sQ0FBb0IsQ0FDYk8sT0FBTyxDQUFDQyxLQUFLLENBQUNSLENBQW9CLENBQUUsSUFBSSxDQUFFSyxDQUFJLENBQUMsU0FMdERKLENBQU0sQ0FBQ1EsTUFBTSxDQUFDLENBQUMsQ0FDUkYsT0FBTyxDQUFDQyxLQUFLLENBQUNULENBQXlCLENBQUUsSUFBSSxDQUFFTSxDQUFJLENBQUMsQ0FNbkUsQ0FDSixDQUVBLE1BQU8sSUFBSSxDQUFBSyxPQUFPLENBQUMsQ0FBQ0MsQ0FBTyxDQUFFQyxDQUFNLEdBQUssQ0FDcEMsS0FBTSxDQUFBWCxDQUFNLENBQUdKLE1BQU0sQ0FBQ0MsQ0FBWSxDQUFFLElBQU0sQ0FDdENjLENBQU0sQ0FBQ2hCLE1BQU0sQ0FBQyxDQUNWTSxPQUFPLENBQUUsQ0FDTEosWUFBWSxDQUFaQSxDQUNKLENBQUMsQ0FDREssT0FBTyxDQUFFLGlCQUFpQkwsQ0FBWSxlQUFlLENBQ3JETSxJQUFJLENBQUUsY0FDVixDQUFDLENBQUMsQ0FDTixDQUFDLENBQUMsQ0FFRkwsQ0FBeUIsQ0FBQ2MsSUFBSSxDQUFDQyxDQUFLLEVBQUksQ0FDL0JiLENBQU0sQ0FBQ0ssU0FBUyxDQUdWTixDQUFvQixFQUMzQkEsQ0FBb0IsQ0FBQyxJQUFJLENBQUVjLENBQUssQ0FBQyxFQUhqQ2IsQ0FBTSxDQUFDUSxNQUFNLENBQUMsQ0FBQyxDQUNmRSxDQUFPLENBQUNHLENBQUssQ0FBQyxDQUl0QixDQUFDLENBQUVDLENBQUssRUFBSSxDQUNIZCxDQUFNLENBQUNLLFNBQVMsQ0FHVk4sQ0FBb0IsRUFDM0JBLENBQW9CLENBQUNlLENBQUssQ0FBQyxFQUgzQmQsQ0FBTSxDQUFDUSxNQUFNLENBQUMsQ0FBQyxDQUNmRyxDQUFNLENBQUNHLENBQUssQ0FBQyxDQUlyQixDQUFDLENBQ0wsQ0FBQyxDQUNMLENBQUMiLCJpZ25vcmVMaXN0IjpbXX0=
1
+ import _TimeoutCancel from 'isotropic-timeout-cancel';
2
+
3
+ export default (timeout, callbackFunctionOrPromise, lateCallbackFunction) => {
4
+ const config = typeof timeout === 'number' || timeout instanceof Temporal.Duration ?
5
+ {
6
+ timeout
7
+ } :
8
+ timeout ?? {};
9
+
10
+ if (typeof callbackFunctionOrPromise === 'function') {
11
+ const cancel = _TimeoutCancel({
12
+ details: config.details,
13
+ onCancel: ({
14
+ error
15
+ }) => {
16
+ if (error) {
17
+ callbackFunctionOrPromise(error);
18
+ }
19
+ },
20
+ signal: config.signal,
21
+ subject: config.subject,
22
+ timeout: config.timeout
23
+ }),
24
+ wrappedCallbackFunction = function (...args) {
25
+ if (!cancel.canceled) {
26
+ cancel.complete();
27
+
28
+ return Reflect.apply(callbackFunctionOrPromise, this, args);
29
+ }
30
+
31
+ if (lateCallbackFunction) {
32
+ return Reflect.apply(lateCallbackFunction, this, args);
33
+ }
34
+ };
35
+
36
+ wrappedCallbackFunction.cancel = cancelConfig => {
37
+ cancel.cancel(cancelConfig);
38
+
39
+ return wrappedCallbackFunction;
40
+ };
41
+
42
+ return wrappedCallbackFunction;
43
+ }
44
+
45
+ {
46
+ const {
47
+ promise,
48
+ reject,
49
+ resolve
50
+ } = Promise.withResolvers(),
51
+ cancel = _TimeoutCancel({
52
+ details: config.details,
53
+ onCancel: ({
54
+ error
55
+ }) => {
56
+ if (error) {
57
+ reject(error);
58
+ }
59
+ },
60
+ signal: config.signal,
61
+ subject: config.subject,
62
+ timeout: config.timeout
63
+ });
64
+
65
+ callbackFunctionOrPromise.then(value => {
66
+ if (cancel.canceled) {
67
+ if (lateCallbackFunction) {
68
+ lateCallbackFunction(null, value);
69
+ }
70
+ } else {
71
+ cancel.complete();
72
+ resolve(value);
73
+ }
74
+ }, error => {
75
+ if (cancel.canceled) {
76
+ if (lateCallbackFunction) {
77
+ lateCallbackFunction(error);
78
+ }
79
+ } else {
80
+ cancel.complete();
81
+ reject(error);
82
+ }
83
+ });
84
+
85
+ promise.cancel = cancelConfig => {
86
+ cancel.cancel(cancelConfig);
87
+
88
+ return promise;
89
+ };
90
+
91
+ return promise;
92
+ }
93
+ };
package/package.json CHANGED
@@ -2,39 +2,40 @@
2
2
  "author": "Steven Olmsted <steven.olmsted@ibigroup.com>",
3
3
  "bugs": "https://github.com/ibi-group/isotropic-timeout/issues",
4
4
  "dependencies": {
5
- "isotropic-error": "~0.13.1",
6
- "isotropic-later": "~0.14.0"
5
+ "isotropic-timeout-cancel": "~0.1.0"
7
6
  },
8
- "description": "Limit the amount of time a callback function will wait to be called",
7
+ "description": "Limit the amount of time a callback function will wait to be called or a promise will wait to settle",
9
8
  "devDependencies": {
10
- "eslint": "~9.8.0",
11
- "isotropic-dev-dependencies": "~0.3.1"
9
+ "isotropic-dev-dependencies": "~0.4.0",
10
+ "isotropic-error": "~0.14.0",
11
+ "isotropic-later": "~0.15.0"
12
12
  },
13
13
  "engines": {
14
- "node": "^22.5.1",
15
- "npm": "^10.8.2"
14
+ "node": "^26.5.0",
15
+ "npm": "^11.17.0"
16
16
  },
17
17
  "files": [
18
18
  "lib"
19
19
  ],
20
20
  "homepage": "https://github.com/ibi-group/isotropic-timeout",
21
21
  "keywords": [
22
- "isotropic"
22
+ "callback",
23
+ "isotropic",
24
+ "promise",
25
+ "timeout"
23
26
  ],
24
27
  "license": "BSD-3-Clause",
25
28
  "main": "lib/timeout.js",
26
29
  "name": "isotropic-timeout",
27
30
  "repository": "github:ibi-group/isotropic-timeout",
28
31
  "scripts": {
29
- "build": "cross-env BABEL_ENV=node-minify babel --config-file ./node_modules/isotropic-dev-dependencies/config/babel.json js -d lib --delete-dir-on-start",
30
- "lint": "eslint js test",
31
- "postprepare": "node ./node_modules/isotropic-dev-dependencies/lib/install-git-hooks.js",
32
- "posttest": "[ -z \"$npm_config_coverage\" ] || c8 -c ./node_modules/isotropic-dev-dependencies/config/c8.json check-coverage",
33
- "prepare": "npm run build",
34
- "prepublishOnly": "npm test --coverage",
32
+ "lint": "eslint eslint.config.js lib test",
33
+ "posttest": "c8 -c ./node_modules/isotropic-dev-dependencies/config/c8.json check-coverage",
34
+ "prepare": "node ./node_modules/isotropic-dev-dependencies/lib/install-git-hooks.js",
35
+ "prepublishOnly": "npm test",
35
36
  "pretest": "npm run lint",
36
- "test": "cross-env BABEL_ENV=test c8 -c ./node_modules/isotropic-dev-dependencies/config/c8.json mocha --import=isotropic-dev-dependencies/lib/register-babel-loader.js"
37
+ "test": "c8 -c ./node_modules/isotropic-dev-dependencies/config/c8.json node --test"
37
38
  },
38
39
  "type": "module",
39
- "version": "0.14.0"
40
+ "version": "0.15.0"
40
41
  }