@naturalcycles/js-lib 15.72.1 → 15.73.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/dist/abort.d.ts +30 -0
- package/dist/abort.js +67 -0
- package/dist/http/fetcher.d.ts +1 -1
- package/dist/http/fetcher.js +11 -23
- package/dist/http/fetcher.model.d.ts +6 -0
- package/dist/promise/index.d.ts +0 -1
- package/dist/promise/index.js +0 -1
- package/dist/promise/pRetry.d.ts +2 -2
- package/dist/promise/pTimeout.d.ts +1 -1
- package/dist/promise/pTimeout.js +6 -2
- package/package.json +1 -1
- package/src/abort.ts +83 -0
- package/src/http/fetcher.model.ts +7 -0
- package/src/http/fetcher.ts +9 -22
- package/src/promise/index.ts +0 -1
- package/src/promise/pRetry.ts +2 -2
- package/src/promise/pTimeout.ts +11 -3
- package/dist/promise/abortable.d.ts +0 -20
- package/dist/promise/abortable.js +0 -32
- package/src/promise/abortable.ts +0 -34
package/dist/abort.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NumberOfMilliseconds } from './types.js';
|
|
1
2
|
/**
|
|
2
3
|
* Like AbortSignal, but it can "abort itself" via the `.abort()` method.
|
|
3
4
|
*
|
|
@@ -22,3 +23,32 @@ export interface AbortableSignal extends AbortSignal {
|
|
|
22
23
|
* @experimental
|
|
23
24
|
*/
|
|
24
25
|
export declare function createAbortableSignal(): AbortableSignal;
|
|
26
|
+
/**
|
|
27
|
+
* Returns AbortSignal if ms is defined.
|
|
28
|
+
* Otherwise returns undefined.
|
|
29
|
+
*/
|
|
30
|
+
export declare function abortSignalTimeoutOrUndefined(ms: NumberOfMilliseconds | undefined): AbortSignal | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Returns an AbortSignal that aborts after the given number of milliseconds.
|
|
33
|
+
* Uses native `AbortSignal.timeout()` when available, falls back to a polyfill.
|
|
34
|
+
*
|
|
35
|
+
* The abort reason is a DOMException with name "TimeoutError".
|
|
36
|
+
*/
|
|
37
|
+
export declare function abortSignalTimeout(ms: NumberOfMilliseconds): AbortSignal;
|
|
38
|
+
export declare function polyfilledAbortSignalTimeout(ms: NumberOfMilliseconds): AbortSignal;
|
|
39
|
+
/**
|
|
40
|
+
* Returns AbortSignal.any(signals) is the array (after filtering undefined inputs) is not empty,
|
|
41
|
+
* otherwise undefined.
|
|
42
|
+
*/
|
|
43
|
+
export declare function abortSignalAnyOrUndefined(signals: (AbortSignal | undefined)[]): AbortSignal | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Returns an AbortSignal that aborts when any of the given signals abort.
|
|
46
|
+
* Uses native `AbortSignal.any()` when available, falls back to a polyfill.
|
|
47
|
+
*
|
|
48
|
+
* The abort reason is taken from the first signal that aborts.
|
|
49
|
+
* If any input signal is already aborted, the returned signal is immediately aborted.
|
|
50
|
+
*
|
|
51
|
+
* If only 1 signal is passed in the input array - that Signal is returned as-is.
|
|
52
|
+
*/
|
|
53
|
+
export declare function abortSignalAny(signals: AbortSignal[]): AbortSignal;
|
|
54
|
+
export declare function polyfilledAbortSignalAny(signals: AbortSignal[]): AbortSignal;
|
package/dist/abort.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _isTruthy } from './is.util.js';
|
|
1
2
|
/**
|
|
2
3
|
* Creates AbortableSignal,
|
|
3
4
|
* which is like AbortSignal, but can "abort itself" with `.abort()` method.
|
|
@@ -10,3 +11,69 @@ export function createAbortableSignal() {
|
|
|
10
11
|
abort: ac.abort.bind(ac),
|
|
11
12
|
});
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns AbortSignal if ms is defined.
|
|
16
|
+
* Otherwise returns undefined.
|
|
17
|
+
*/
|
|
18
|
+
export function abortSignalTimeoutOrUndefined(ms) {
|
|
19
|
+
return ms ? abortSignalTimeout(ms) : undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns an AbortSignal that aborts after the given number of milliseconds.
|
|
23
|
+
* Uses native `AbortSignal.timeout()` when available, falls back to a polyfill.
|
|
24
|
+
*
|
|
25
|
+
* The abort reason is a DOMException with name "TimeoutError".
|
|
26
|
+
*/
|
|
27
|
+
export function abortSignalTimeout(ms) {
|
|
28
|
+
return typeof AbortSignal.timeout === 'function'
|
|
29
|
+
? AbortSignal.timeout(ms)
|
|
30
|
+
: polyfilledAbortSignalTimeout(ms);
|
|
31
|
+
}
|
|
32
|
+
export function polyfilledAbortSignalTimeout(ms) {
|
|
33
|
+
const ac = new AbortController();
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
ac.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError'));
|
|
36
|
+
}, ms);
|
|
37
|
+
return ac.signal;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns AbortSignal.any(signals) is the array (after filtering undefined inputs) is not empty,
|
|
41
|
+
* otherwise undefined.
|
|
42
|
+
*/
|
|
43
|
+
export function abortSignalAnyOrUndefined(signals) {
|
|
44
|
+
const filtered = signals.filter(_isTruthy);
|
|
45
|
+
return filtered.length ? abortSignalAny(filtered) : undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns an AbortSignal that aborts when any of the given signals abort.
|
|
49
|
+
* Uses native `AbortSignal.any()` when available, falls back to a polyfill.
|
|
50
|
+
*
|
|
51
|
+
* The abort reason is taken from the first signal that aborts.
|
|
52
|
+
* If any input signal is already aborted, the returned signal is immediately aborted.
|
|
53
|
+
*
|
|
54
|
+
* If only 1 signal is passed in the input array - that Signal is returned as-is.
|
|
55
|
+
*/
|
|
56
|
+
export function abortSignalAny(signals) {
|
|
57
|
+
if (signals.length === 1) {
|
|
58
|
+
return signals[0];
|
|
59
|
+
}
|
|
60
|
+
return typeof AbortSignal.any === 'function'
|
|
61
|
+
? AbortSignal.any(signals)
|
|
62
|
+
: polyfilledAbortSignalAny(signals);
|
|
63
|
+
}
|
|
64
|
+
export function polyfilledAbortSignalAny(signals) {
|
|
65
|
+
const ac = new AbortController();
|
|
66
|
+
for (const signal of signals) {
|
|
67
|
+
if (signal.aborted) {
|
|
68
|
+
ac.abort(signal.reason);
|
|
69
|
+
return ac.signal;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const signal of signals) {
|
|
73
|
+
signal.addEventListener('abort', () => ac.abort(signal.reason), {
|
|
74
|
+
once: true,
|
|
75
|
+
signal: ac.signal,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return ac.signal;
|
|
79
|
+
}
|
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class Fetcher {
|
|
|
16
16
|
*
|
|
17
17
|
* Version is to be incremented every time a difference in behaviour (or a bugfix) is done.
|
|
18
18
|
*/
|
|
19
|
-
static readonly VERSION =
|
|
19
|
+
static readonly VERSION = 4;
|
|
20
20
|
/**
|
|
21
21
|
* userAgent is statically exposed as Fetcher.userAgent.
|
|
22
22
|
* It can be modified globally, and will be used (read) at the start of every request.
|
package/dist/http/fetcher.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/// <reference lib="es2023" preserve="true" />
|
|
2
2
|
/// <reference lib="dom" preserve="true" />
|
|
3
3
|
/// <reference lib="dom.iterable" preserve="true" />
|
|
4
|
+
import { abortSignalAnyOrUndefined, abortSignalTimeoutOrUndefined } from '../abort.js';
|
|
4
5
|
import { _ms, _since } from '../datetime/time.util.js';
|
|
5
6
|
import { isServerSide } from '../env.js';
|
|
6
7
|
import { _assertErrorClassOrRethrow, _assertIsError } from '../error/assert.js';
|
|
7
|
-
import { _anyToError, _anyToErrorObject, _errorDataAppend, _errorLikeToErrorObject, HttpRequestError,
|
|
8
|
+
import { _anyToError, _anyToErrorObject, _errorDataAppend, _errorLikeToErrorObject, HttpRequestError, UnexpectedPassError, } from '../error/error.util.js';
|
|
8
9
|
import { _clamp } from '../number/number.util.js';
|
|
9
10
|
import { _filterFalsyValues, _filterNullishValues, _filterUndefinedValues, _mapKeys, _merge, _omit, _pick, } from '../object/object.util.js';
|
|
10
11
|
import { pDelay } from '../promise/pDelay.js';
|
|
@@ -24,7 +25,7 @@ export class Fetcher {
|
|
|
24
25
|
*
|
|
25
26
|
* Version is to be incremented every time a difference in behaviour (or a bugfix) is done.
|
|
26
27
|
*/
|
|
27
|
-
static VERSION =
|
|
28
|
+
static VERSION = 4;
|
|
28
29
|
/**
|
|
29
30
|
* userAgent is statically exposed as Fetcher.userAgent.
|
|
30
31
|
* It can be modified globally, and will be used (read) at the start of every request.
|
|
@@ -232,7 +233,8 @@ export class Fetcher {
|
|
|
232
233
|
async doFetch(opt) {
|
|
233
234
|
const req = this.normalizeOptions(opt);
|
|
234
235
|
const { logger } = this.cfg;
|
|
235
|
-
const {
|
|
236
|
+
const { init: { method }, } = req;
|
|
237
|
+
const timeoutMillis = req.timeoutSeconds ? req.timeoutSeconds * 1000 : undefined;
|
|
236
238
|
for (const hook of this.cfg.hooks.beforeRequest || []) {
|
|
237
239
|
await hook(req);
|
|
238
240
|
}
|
|
@@ -251,21 +253,10 @@ export class Fetcher {
|
|
|
251
253
|
};
|
|
252
254
|
while (!res.retryStatus.retryStopped) {
|
|
253
255
|
req.started = Date.now();
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// but also for "downloadBody" timeout (even after request returned with 200, but before we loaded the body)
|
|
259
|
-
// UPD: no, not using for "downloadBody" currently
|
|
260
|
-
const abortController = new AbortController();
|
|
261
|
-
req.init.signal = abortController.signal;
|
|
262
|
-
timeoutId = setTimeout(() => {
|
|
263
|
-
// console.log(`actual request timed out in ${_since(req.started)}`)
|
|
264
|
-
// Apparently, providing a `string` reason to abort() causes Undici to throw `invalid_argument` error,
|
|
265
|
-
// so, we're wrapping it in a TimeoutError instance
|
|
266
|
-
abortController.abort(new TimeoutError(`request timed out after ${timeoutSeconds} sec`));
|
|
267
|
-
}, timeoutSeconds * 1000);
|
|
268
|
-
}
|
|
256
|
+
req.init.signal = abortSignalAnyOrUndefined([
|
|
257
|
+
abortSignalTimeoutOrUndefined(timeoutMillis),
|
|
258
|
+
opt.signal,
|
|
259
|
+
]);
|
|
269
260
|
if (req.logRequest) {
|
|
270
261
|
const { retryAttempt } = res.retryStatus;
|
|
271
262
|
logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`]
|
|
@@ -284,22 +275,19 @@ export class Fetcher {
|
|
|
284
275
|
catch (err) {
|
|
285
276
|
// For example, CORS error would result in "TypeError: failed to fetch" here
|
|
286
277
|
// or, `fetch failed` with the cause of `unexpected redirect`
|
|
278
|
+
// AbortSignal.timeout() throws a DOMException with name "TimeoutError"
|
|
287
279
|
res.err = _anyToError(err);
|
|
288
280
|
res.ok = false;
|
|
289
281
|
// important to set it to undefined, otherwise it can keep the previous value (from previous try)
|
|
290
282
|
res.fetchResponse = undefined;
|
|
291
283
|
}
|
|
292
|
-
finally {
|
|
293
|
-
clearTimeout(timeoutId);
|
|
294
|
-
// Separate Timeout will be introduced to "download and parse the body"
|
|
295
|
-
}
|
|
296
284
|
res.statusFamily = this.getStatusFamily(res);
|
|
297
285
|
res.statusCode = res.fetchResponse?.status;
|
|
298
286
|
if (res.fetchResponse?.ok || !req.throwHttpErrors) {
|
|
299
287
|
try {
|
|
300
288
|
// We are applying a separate Timeout (as long as original Timeout for now) to "download and parse the body"
|
|
301
289
|
await pTimeout(async () => await this.onOkResponse(res), {
|
|
302
|
-
timeout:
|
|
290
|
+
timeout: timeoutMillis || Number.POSITIVE_INFINITY,
|
|
303
291
|
name: 'Fetcher.downloadBody',
|
|
304
292
|
});
|
|
305
293
|
}
|
|
@@ -169,6 +169,12 @@ export interface FetcherOptions {
|
|
|
169
169
|
* so both should finish within this single timeout (not each).
|
|
170
170
|
*/
|
|
171
171
|
timeoutSeconds?: number;
|
|
172
|
+
/**
|
|
173
|
+
* AbortSignal to allow the caller to abort the request.
|
|
174
|
+
* If `timeoutSeconds` is also set, the signals are combined via `AbortSignal.any()`,
|
|
175
|
+
* so the request aborts on whichever fires first.
|
|
176
|
+
*/
|
|
177
|
+
signal?: AbortSignal;
|
|
172
178
|
/**
|
|
173
179
|
* Supports all the types that RequestInit.body supports.
|
|
174
180
|
*
|
package/dist/promise/index.d.ts
CHANGED
package/dist/promise/index.js
CHANGED
package/dist/promise/pRetry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ErrorData } from '../error/error.model.js';
|
|
2
2
|
import type { CommonLogger } from '../log/commonLogger.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { AnyAsyncFunction } from '../types.js';
|
|
4
4
|
export interface PRetryOptions {
|
|
5
5
|
/**
|
|
6
6
|
* If set - will be included in the error message.
|
|
@@ -79,5 +79,5 @@ export interface PRetryOptions {
|
|
|
79
79
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
80
80
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
81
81
|
*/
|
|
82
|
-
export declare function pRetryFn<T extends
|
|
82
|
+
export declare function pRetryFn<T extends AnyAsyncFunction>(fn: T, opt?: PRetryOptions): T;
|
|
83
83
|
export declare function pRetry<T>(fn: (attempt: number) => Promise<T>, opt?: PRetryOptions): Promise<T>;
|
|
@@ -46,4 +46,4 @@ export declare function pTimeoutFn<T extends AnyAsyncFunction>(fn: T, opt: PTime
|
|
|
46
46
|
* Throws an Error if the Function is not resolved in a certain time.
|
|
47
47
|
* If the Function rejects - passes this rejection further.
|
|
48
48
|
*/
|
|
49
|
-
export declare function pTimeout<T>(fn:
|
|
49
|
+
export declare function pTimeout<T>(fn: (signal: AbortSignal) => Promise<T>, opt: PTimeoutOptions): Promise<T>;
|
package/dist/promise/pTimeout.js
CHANGED
|
@@ -23,9 +23,11 @@ export function pTimeoutFn(fn, opt) {
|
|
|
23
23
|
* If the Function rejects - passes this rejection further.
|
|
24
24
|
*/
|
|
25
25
|
export async function pTimeout(fn, opt) {
|
|
26
|
+
const ac = new AbortController();
|
|
27
|
+
const { signal } = ac;
|
|
26
28
|
if (!opt.timeout) {
|
|
27
29
|
// short-circuit to direct execution if 0 timeout is passed
|
|
28
|
-
return await fn();
|
|
30
|
+
return await fn(signal);
|
|
29
31
|
}
|
|
30
32
|
const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt;
|
|
31
33
|
const fakeError = opt.fakeError || new Error('TimeoutError');
|
|
@@ -46,13 +48,15 @@ export async function pTimeout(fn, opt) {
|
|
|
46
48
|
// oxlint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
47
49
|
reject(_errorDataAppend(err, opt.errorData));
|
|
48
50
|
}
|
|
51
|
+
ac.abort(err);
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
54
|
reject(err);
|
|
55
|
+
ac.abort(err);
|
|
52
56
|
}, timeout);
|
|
53
57
|
// Execute the Function
|
|
54
58
|
try {
|
|
55
|
-
resolve(await fn());
|
|
59
|
+
resolve(await fn(signal));
|
|
56
60
|
}
|
|
57
61
|
catch (err) {
|
|
58
62
|
reject(err);
|
package/package.json
CHANGED
package/src/abort.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { _isTruthy } from './is.util.js'
|
|
2
|
+
import type { NumberOfMilliseconds } from './types.js'
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Like AbortSignal, but it can "abort itself" via the `.abort()` method.
|
|
3
6
|
*
|
|
@@ -28,3 +31,83 @@ export function createAbortableSignal(): AbortableSignal {
|
|
|
28
31
|
abort: ac.abort.bind(ac),
|
|
29
32
|
})
|
|
30
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns AbortSignal if ms is defined.
|
|
37
|
+
* Otherwise returns undefined.
|
|
38
|
+
*/
|
|
39
|
+
export function abortSignalTimeoutOrUndefined(
|
|
40
|
+
ms: NumberOfMilliseconds | undefined,
|
|
41
|
+
): AbortSignal | undefined {
|
|
42
|
+
return ms ? abortSignalTimeout(ms) : undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns an AbortSignal that aborts after the given number of milliseconds.
|
|
47
|
+
* Uses native `AbortSignal.timeout()` when available, falls back to a polyfill.
|
|
48
|
+
*
|
|
49
|
+
* The abort reason is a DOMException with name "TimeoutError".
|
|
50
|
+
*/
|
|
51
|
+
export function abortSignalTimeout(ms: NumberOfMilliseconds): AbortSignal {
|
|
52
|
+
return typeof AbortSignal.timeout === 'function'
|
|
53
|
+
? AbortSignal.timeout(ms)
|
|
54
|
+
: polyfilledAbortSignalTimeout(ms)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function polyfilledAbortSignalTimeout(ms: NumberOfMilliseconds): AbortSignal {
|
|
58
|
+
const ac = new AbortController()
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
ac.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
|
|
61
|
+
}, ms)
|
|
62
|
+
return ac.signal
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns AbortSignal.any(signals) is the array (after filtering undefined inputs) is not empty,
|
|
67
|
+
* otherwise undefined.
|
|
68
|
+
*/
|
|
69
|
+
export function abortSignalAnyOrUndefined(
|
|
70
|
+
signals: (AbortSignal | undefined)[],
|
|
71
|
+
): AbortSignal | undefined {
|
|
72
|
+
const filtered = signals.filter(_isTruthy)
|
|
73
|
+
return filtered.length ? abortSignalAny(filtered) : undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns an AbortSignal that aborts when any of the given signals abort.
|
|
78
|
+
* Uses native `AbortSignal.any()` when available, falls back to a polyfill.
|
|
79
|
+
*
|
|
80
|
+
* The abort reason is taken from the first signal that aborts.
|
|
81
|
+
* If any input signal is already aborted, the returned signal is immediately aborted.
|
|
82
|
+
*
|
|
83
|
+
* If only 1 signal is passed in the input array - that Signal is returned as-is.
|
|
84
|
+
*/
|
|
85
|
+
export function abortSignalAny(signals: AbortSignal[]): AbortSignal {
|
|
86
|
+
if (signals.length === 1) {
|
|
87
|
+
return signals[0]!
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return typeof AbortSignal.any === 'function'
|
|
91
|
+
? AbortSignal.any(signals)
|
|
92
|
+
: polyfilledAbortSignalAny(signals)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function polyfilledAbortSignalAny(signals: AbortSignal[]): AbortSignal {
|
|
96
|
+
const ac = new AbortController()
|
|
97
|
+
|
|
98
|
+
for (const signal of signals) {
|
|
99
|
+
if (signal.aborted) {
|
|
100
|
+
ac.abort(signal.reason)
|
|
101
|
+
return ac.signal
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const signal of signals) {
|
|
106
|
+
signal.addEventListener('abort', () => ac.abort(signal.reason), {
|
|
107
|
+
once: true,
|
|
108
|
+
signal: ac.signal,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return ac.signal
|
|
113
|
+
}
|
|
@@ -221,6 +221,13 @@ export interface FetcherOptions {
|
|
|
221
221
|
*/
|
|
222
222
|
timeoutSeconds?: number
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* AbortSignal to allow the caller to abort the request.
|
|
226
|
+
* If `timeoutSeconds` is also set, the signals are combined via `AbortSignal.any()`,
|
|
227
|
+
* so the request aborts on whichever fires first.
|
|
228
|
+
*/
|
|
229
|
+
signal?: AbortSignal
|
|
230
|
+
|
|
224
231
|
/**
|
|
225
232
|
* Supports all the types that RequestInit.body supports.
|
|
226
233
|
*
|
package/src/http/fetcher.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/// <reference lib="dom.iterable" preserve="true" />
|
|
4
4
|
|
|
5
5
|
import type { ReadableStream as WebReadableStream } from 'node:stream/web'
|
|
6
|
+
import { abortSignalAnyOrUndefined, abortSignalTimeoutOrUndefined } from '../abort.js'
|
|
6
7
|
import { _ms, _since } from '../datetime/time.util.js'
|
|
7
8
|
import { isServerSide } from '../env.js'
|
|
8
9
|
import { _assertErrorClassOrRethrow, _assertIsError } from '../error/assert.js'
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
_errorDataAppend,
|
|
14
15
|
_errorLikeToErrorObject,
|
|
15
16
|
HttpRequestError,
|
|
16
|
-
TimeoutError,
|
|
17
17
|
UnexpectedPassError,
|
|
18
18
|
} from '../error/error.util.js'
|
|
19
19
|
import { _clamp } from '../number/number.util.js'
|
|
@@ -68,7 +68,7 @@ export class Fetcher {
|
|
|
68
68
|
*
|
|
69
69
|
* Version is to be incremented every time a difference in behaviour (or a bugfix) is done.
|
|
70
70
|
*/
|
|
71
|
-
static readonly VERSION =
|
|
71
|
+
static readonly VERSION = 4
|
|
72
72
|
/**
|
|
73
73
|
* userAgent is statically exposed as Fetcher.userAgent.
|
|
74
74
|
* It can be modified globally, and will be used (read) at the start of every request.
|
|
@@ -306,9 +306,9 @@ export class Fetcher {
|
|
|
306
306
|
const req = this.normalizeOptions(opt)
|
|
307
307
|
const { logger } = this.cfg
|
|
308
308
|
const {
|
|
309
|
-
timeoutSeconds,
|
|
310
309
|
init: { method },
|
|
311
310
|
} = req
|
|
311
|
+
const timeoutMillis = req.timeoutSeconds ? req.timeoutSeconds * 1000 : undefined
|
|
312
312
|
|
|
313
313
|
for (const hook of this.cfg.hooks.beforeRequest || []) {
|
|
314
314
|
await hook(req)
|
|
@@ -332,21 +332,10 @@ export class Fetcher {
|
|
|
332
332
|
while (!res.retryStatus.retryStopped) {
|
|
333
333
|
req.started = Date.now() as UnixTimestampMillis
|
|
334
334
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// but also for "downloadBody" timeout (even after request returned with 200, but before we loaded the body)
|
|
340
|
-
// UPD: no, not using for "downloadBody" currently
|
|
341
|
-
const abortController = new AbortController()
|
|
342
|
-
req.init.signal = abortController.signal
|
|
343
|
-
timeoutId = setTimeout(() => {
|
|
344
|
-
// console.log(`actual request timed out in ${_since(req.started)}`)
|
|
345
|
-
// Apparently, providing a `string` reason to abort() causes Undici to throw `invalid_argument` error,
|
|
346
|
-
// so, we're wrapping it in a TimeoutError instance
|
|
347
|
-
abortController.abort(new TimeoutError(`request timed out after ${timeoutSeconds} sec`))
|
|
348
|
-
}, timeoutSeconds * 1000) as any as number
|
|
349
|
-
}
|
|
335
|
+
req.init.signal = abortSignalAnyOrUndefined([
|
|
336
|
+
abortSignalTimeoutOrUndefined(timeoutMillis),
|
|
337
|
+
opt.signal,
|
|
338
|
+
])
|
|
350
339
|
|
|
351
340
|
if (req.logRequest) {
|
|
352
341
|
const { retryAttempt } = res.retryStatus
|
|
@@ -372,13 +361,11 @@ export class Fetcher {
|
|
|
372
361
|
} catch (err) {
|
|
373
362
|
// For example, CORS error would result in "TypeError: failed to fetch" here
|
|
374
363
|
// or, `fetch failed` with the cause of `unexpected redirect`
|
|
364
|
+
// AbortSignal.timeout() throws a DOMException with name "TimeoutError"
|
|
375
365
|
res.err = _anyToError(err)
|
|
376
366
|
res.ok = false
|
|
377
367
|
// important to set it to undefined, otherwise it can keep the previous value (from previous try)
|
|
378
368
|
res.fetchResponse = undefined
|
|
379
|
-
} finally {
|
|
380
|
-
clearTimeout(timeoutId)
|
|
381
|
-
// Separate Timeout will be introduced to "download and parse the body"
|
|
382
369
|
}
|
|
383
370
|
res.statusFamily = this.getStatusFamily(res)
|
|
384
371
|
res.statusCode = res.fetchResponse?.status
|
|
@@ -390,7 +377,7 @@ export class Fetcher {
|
|
|
390
377
|
async () =>
|
|
391
378
|
await this.onOkResponse(res as FetcherResponse<T> & { fetchResponse: Response }),
|
|
392
379
|
{
|
|
393
|
-
timeout:
|
|
380
|
+
timeout: timeoutMillis || Number.POSITIVE_INFINITY,
|
|
394
381
|
name: 'Fetcher.downloadBody',
|
|
395
382
|
},
|
|
396
383
|
)
|
package/src/promise/index.ts
CHANGED
package/src/promise/pRetry.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { _since } from '../datetime/time.util.js'
|
|
|
2
2
|
import type { ErrorData } from '../error/error.model.js'
|
|
3
3
|
import { _errorDataAppend } from '../error/error.util.js'
|
|
4
4
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
5
|
-
import type {
|
|
5
|
+
import type { AnyAsyncFunction, UnixTimestampMillis } from '../types.js'
|
|
6
6
|
import { pDelay } from './pDelay.js'
|
|
7
7
|
import { pTimeout } from './pTimeout.js'
|
|
8
8
|
|
|
@@ -98,7 +98,7 @@ export interface PRetryOptions {
|
|
|
98
98
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
99
99
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
100
100
|
*/
|
|
101
|
-
export function pRetryFn<T extends
|
|
101
|
+
export function pRetryFn<T extends AnyAsyncFunction>(fn: T, opt: PRetryOptions = {}): T {
|
|
102
102
|
return async function pRetryFunction(this: any, ...args: any[]) {
|
|
103
103
|
return await pRetry(() => fn.call(this, ...args), opt)
|
|
104
104
|
} as any
|
package/src/promise/pTimeout.ts
CHANGED
|
@@ -64,10 +64,16 @@ export function pTimeoutFn<T extends AnyAsyncFunction>(fn: T, opt: PTimeoutOptio
|
|
|
64
64
|
* Throws an Error if the Function is not resolved in a certain time.
|
|
65
65
|
* If the Function rejects - passes this rejection further.
|
|
66
66
|
*/
|
|
67
|
-
export async function pTimeout<T>(
|
|
67
|
+
export async function pTimeout<T>(
|
|
68
|
+
fn: (signal: AbortSignal) => Promise<T>,
|
|
69
|
+
opt: PTimeoutOptions,
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
const ac = new AbortController()
|
|
72
|
+
const { signal } = ac
|
|
73
|
+
|
|
68
74
|
if (!opt.timeout) {
|
|
69
75
|
// short-circuit to direct execution if 0 timeout is passed
|
|
70
|
-
return await fn()
|
|
76
|
+
return await fn(signal)
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt
|
|
@@ -90,15 +96,17 @@ export async function pTimeout<T>(fn: AnyAsyncFunction<T>, opt: PTimeoutOptions)
|
|
|
90
96
|
// oxlint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
91
97
|
reject(_errorDataAppend(err, opt.errorData))
|
|
92
98
|
}
|
|
99
|
+
ac.abort(err)
|
|
93
100
|
return
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
reject(err)
|
|
104
|
+
ac.abort(err)
|
|
97
105
|
}, timeout)
|
|
98
106
|
|
|
99
107
|
// Execute the Function
|
|
100
108
|
try {
|
|
101
|
-
resolve(await fn())
|
|
109
|
+
resolve(await fn(signal))
|
|
102
110
|
} catch (err) {
|
|
103
111
|
reject(err as Error)
|
|
104
112
|
} finally {
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { AnyFunction } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Similar to AbortController and AbortSignal.
|
|
4
|
-
* Similar to pDefer and Promise.
|
|
5
|
-
* Similar to Subject and Observable.
|
|
6
|
-
*
|
|
7
|
-
* Minimal interface for something that can be aborted in the future,
|
|
8
|
-
* but not necessary.
|
|
9
|
-
* Allows to listen to `onAbort` event.
|
|
10
|
-
*
|
|
11
|
-
* @experimental
|
|
12
|
-
*/
|
|
13
|
-
export declare class Abortable {
|
|
14
|
-
onAbort?: AnyFunction | undefined;
|
|
15
|
-
constructor(onAbort?: AnyFunction | undefined);
|
|
16
|
-
aborted: boolean;
|
|
17
|
-
abort(): void;
|
|
18
|
-
clear(): void;
|
|
19
|
-
}
|
|
20
|
-
export declare function abortable(onAbort?: AnyFunction): Abortable;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Similar to AbortController and AbortSignal.
|
|
3
|
-
* Similar to pDefer and Promise.
|
|
4
|
-
* Similar to Subject and Observable.
|
|
5
|
-
*
|
|
6
|
-
* Minimal interface for something that can be aborted in the future,
|
|
7
|
-
* but not necessary.
|
|
8
|
-
* Allows to listen to `onAbort` event.
|
|
9
|
-
*
|
|
10
|
-
* @experimental
|
|
11
|
-
*/
|
|
12
|
-
export class Abortable {
|
|
13
|
-
onAbort;
|
|
14
|
-
constructor(onAbort) {
|
|
15
|
-
this.onAbort = onAbort;
|
|
16
|
-
}
|
|
17
|
-
aborted = false;
|
|
18
|
-
abort() {
|
|
19
|
-
if (this.aborted)
|
|
20
|
-
return;
|
|
21
|
-
this.aborted = true;
|
|
22
|
-
this.onAbort?.();
|
|
23
|
-
this.onAbort = undefined; // cleanup listener
|
|
24
|
-
}
|
|
25
|
-
clear() {
|
|
26
|
-
this.onAbort = undefined;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// convenience function
|
|
30
|
-
export function abortable(onAbort) {
|
|
31
|
-
return new Abortable(onAbort);
|
|
32
|
-
}
|
package/src/promise/abortable.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { AnyFunction } from '../types.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Similar to AbortController and AbortSignal.
|
|
5
|
-
* Similar to pDefer and Promise.
|
|
6
|
-
* Similar to Subject and Observable.
|
|
7
|
-
*
|
|
8
|
-
* Minimal interface for something that can be aborted in the future,
|
|
9
|
-
* but not necessary.
|
|
10
|
-
* Allows to listen to `onAbort` event.
|
|
11
|
-
*
|
|
12
|
-
* @experimental
|
|
13
|
-
*/
|
|
14
|
-
export class Abortable {
|
|
15
|
-
constructor(public onAbort?: AnyFunction) {}
|
|
16
|
-
|
|
17
|
-
aborted = false
|
|
18
|
-
|
|
19
|
-
abort(): void {
|
|
20
|
-
if (this.aborted) return
|
|
21
|
-
this.aborted = true
|
|
22
|
-
this.onAbort?.()
|
|
23
|
-
this.onAbort = undefined // cleanup listener
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
clear(): void {
|
|
27
|
-
this.onAbort = undefined
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// convenience function
|
|
32
|
-
export function abortable(onAbort?: AnyFunction): Abortable {
|
|
33
|
-
return new Abortable(onAbort)
|
|
34
|
-
}
|