saborter 1.2.0 → 1.4.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/index.cjs.js CHANGED
@@ -1,37 +1,182 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const ABORT_ERROR_MESSAGE = "The user aborted a request.";
4
- const ABORT_ERROR_WITHOUT_REASON_MESSAGE = "signal is aborted without reason";
3
+ const EMIT_METHOD_SYMBOL = /* @__PURE__ */ Symbol("STATE-OBSERVER:EMIT_METHOD");
4
+ const CLEAR_METHOD_SYMBOL$1 = /* @__PURE__ */ Symbol("STATE-OBSERVER:CLEAR_METHOD");
5
+ var _a$1, _b;
6
+ _b = EMIT_METHOD_SYMBOL, _a$1 = CLEAR_METHOD_SYMBOL$1;
7
+ class StateObserver {
8
+ constructor(options) {
9
+ this.subscribers = /* @__PURE__ */ new Set();
10
+ this.subscribe = (callbackfn) => {
11
+ this.subscribers.add(callbackfn);
12
+ return () => this.unsubscribe(callbackfn);
13
+ };
14
+ this.unsubscribe = (callbackfn) => {
15
+ this.subscribers.delete(callbackfn);
16
+ };
17
+ this[_b] = (state) => {
18
+ this.value = state;
19
+ this.subscribers.forEach((subscribe) => {
20
+ subscribe(state);
21
+ });
22
+ this.onstatechange?.(state);
23
+ };
24
+ this[_a$1] = () => {
25
+ this.subscribers = /* @__PURE__ */ new Set();
26
+ this.onstatechange = void 0;
27
+ this.value = void 0;
28
+ };
29
+ this.onstatechange = options?.onStateChange;
30
+ }
31
+ }
32
+ const emitRequestState = (instance, requestState) => {
33
+ instance[EMIT_METHOD_SYMBOL](requestState);
34
+ };
35
+ const clearStateListeners = (instance) => {
36
+ instance[CLEAR_METHOD_SYMBOL$1]();
37
+ };
5
38
  const ABORT_ERROR_NAME = "AbortError";
6
39
  const ERROR_CAUSE_PATH_NAME = "cause.name";
7
40
  const ERROR_CAUSE_PATH_MESSAGE = "cause.message";
8
- const ABORT_ERROR_MESSAGES = [
9
- ABORT_ERROR_MESSAGE,
10
- ABORT_ERROR_WITHOUT_REASON_MESSAGE
11
- ];
12
- const get = (object, path) => path.split(".").reduce(
13
- (acc, key) => acc && acc[key],
14
- object
15
- );
16
- const isError = (error) => ABORT_ERROR_MESSAGES.includes(
17
- error?.message ?? ""
18
- ) || get(error, ERROR_CAUSE_PATH_NAME) === ABORT_ERROR_NAME || ABORT_ERROR_MESSAGES.includes(
19
- get(error, ERROR_CAUSE_PATH_MESSAGE)
20
- );
21
- const _Aborter = class _Aborter {
41
+ class AbortError extends Error {
42
+ constructor(message, options) {
43
+ super(message);
44
+ this.code = 20;
45
+ this.timestamp = Date.now();
46
+ this.name = ABORT_ERROR_NAME;
47
+ this.type = options?.type || "aborted";
48
+ this.reason = options?.reason;
49
+ this.signal = options?.signal;
50
+ this.cause = options?.cause;
51
+ this.initiator = options?.initiator || "user";
52
+ }
53
+ }
54
+ const get = (object, path) => path.split(".").reduce((acc, key) => acc && acc[key], object);
55
+ const isObject = (value) => {
56
+ return value !== null && !Array.isArray(value) && typeof value === "object";
57
+ };
58
+ const checkErrorCause = (error) => get(error, ERROR_CAUSE_PATH_NAME) === ABORT_ERROR_NAME || "abort".includes(get(error, ERROR_CAUSE_PATH_MESSAGE));
59
+ const isError = (error) => error instanceof AbortError || isObject(error) && "name" in error && error.name === ABORT_ERROR_NAME || "abort".includes(error?.message ?? "") || checkErrorCause(error);
60
+ class Timeout {
22
61
  constructor() {
62
+ this.setTimeout = (timeout, onAbort) => {
63
+ this.clearTimeout();
64
+ if (!timeout || timeout <= 0) return;
65
+ this.timeoutId = setTimeout(onAbort, timeout);
66
+ };
67
+ this.clearTimeout = () => {
68
+ if (this.timeoutId) {
69
+ clearTimeout(this.timeoutId);
70
+ this.timeoutId = void 0;
71
+ }
72
+ };
73
+ }
74
+ }
75
+ class TimeoutError extends Error {
76
+ constructor(message, options) {
77
+ super(message);
78
+ this.timestamp = Date.now();
79
+ this.hasThrow = options?.hasThrow ?? false;
80
+ this.ms = options?.ms;
81
+ }
82
+ }
83
+ var ErrorMessage = /* @__PURE__ */ ((ErrorMessage2) => {
84
+ ErrorMessage2["AbortedSignalWithoutMessage"] = "signal is aborted without message";
85
+ ErrorMessage2["RequestTimedout"] = "the request timed out and an automatic abort occurred";
86
+ ErrorMessage2["CancelRequest"] = "cancellation of the previous AbortController";
87
+ return ErrorMessage2;
88
+ })(ErrorMessage || {});
89
+ const CLEAR_METHOD_SYMBOL = /* @__PURE__ */ Symbol("EVENT_LISTENER:CLEAR_METHOD");
90
+ var _a;
91
+ _a = CLEAR_METHOD_SYMBOL;
92
+ class EventListener {
93
+ constructor(options) {
94
+ this.listeners = {};
95
+ this.state = new StateObserver();
96
+ this.getListenersByType = (type) => {
97
+ var _a2;
98
+ (_a2 = this.listeners)[type] ?? (_a2[type] = /* @__PURE__ */ new Set());
99
+ return this.listeners[type];
100
+ };
101
+ this.addEventListener = (type, listener) => {
102
+ this.getListenersByType(type).add(listener);
103
+ return () => this.removeEventListener(type, listener);
104
+ };
105
+ this.removeEventListener = (type, listener) => {
106
+ this.getListenersByType(type).delete(listener);
107
+ };
108
+ this.dispatchEvent = (type, event) => {
109
+ if (type === "aborted" || type === "cancelled") {
110
+ this.onabort?.(event);
111
+ }
112
+ this.getListenersByType(type).forEach((listener) => listener(event));
113
+ };
114
+ this[_a] = () => {
115
+ this.listeners = {};
116
+ this.onabort = void 0;
117
+ clearStateListeners(this.state);
118
+ };
119
+ this.onabort = options?.onAbort;
120
+ this.state.onstatechange = options?.onStateChange;
121
+ }
122
+ }
123
+ const clearEventListeners = (instance) => {
124
+ instance[CLEAR_METHOD_SYMBOL]();
125
+ };
126
+ const getAbortErrorByReason = (reason) => {
127
+ if (reason === void 0) {
128
+ return void 0;
129
+ }
130
+ if (reason instanceof AbortError) {
131
+ return reason;
132
+ }
133
+ return new AbortError(get(reason, "message") || ErrorMessage.AbortedSignalWithoutMessage, {
134
+ reason
135
+ });
136
+ };
137
+ const hasThrowInTimeoutError = (error) => {
138
+ return error instanceof AbortError && error.cause instanceof TimeoutError && error.cause.hasThrow;
139
+ };
140
+ const _Aborter = class _Aborter {
141
+ constructor(options) {
23
142
  this.abortController = new AbortController();
24
- this.try = (request, { isErrorNativeBehavior = false } = {}) => {
143
+ this.isRequestInProgress = false;
144
+ this.timeout = new Timeout();
145
+ this.setRequestState = (state) => {
146
+ emitRequestState(this.listeners.state, state);
147
+ if (["fulfilled", "rejected", "aborted"].indexOf(state) !== -1) {
148
+ this.timeout.clearTimeout();
149
+ this.isRequestInProgress = false;
150
+ }
151
+ };
152
+ this.try = (request, { isErrorNativeBehavior = false, timeout } = {}) => {
153
+ if (this.isRequestInProgress) {
154
+ const cancelledAbortError = new AbortError(ErrorMessage.CancelRequest, {
155
+ type: "cancelled",
156
+ signal: this.signal,
157
+ initiator: "system"
158
+ });
159
+ this.setRequestState("cancelled");
160
+ this.abort(cancelledAbortError);
161
+ }
25
162
  let promise = new Promise((resolve, reject) => {
26
- this.abort();
27
163
  this.abortController = new AbortController();
28
- const { signal } = this.abortController;
29
- request(signal).then(resolve).catch((err) => {
30
- const error = {
31
- ...err,
32
- message: err?.message || get(err, ERROR_CAUSE_PATH_MESSAGE) || ""
33
- };
34
- if (isErrorNativeBehavior || !_Aborter.isError(err)) {
164
+ this.isRequestInProgress = true;
165
+ this.timeout.setTimeout(timeout?.ms, () => {
166
+ const abortError = new AbortError(ErrorMessage.RequestTimedout, {
167
+ initiator: "timeout",
168
+ cause: new TimeoutError(ErrorMessage.RequestTimedout, timeout)
169
+ });
170
+ this.abort(abortError);
171
+ });
172
+ queueMicrotask(() => this.setRequestState("pending"));
173
+ request(this.abortController.signal).then((response) => {
174
+ if (!this.isRequestInProgress) return;
175
+ this.setRequestState("fulfilled");
176
+ resolve(response);
177
+ }).catch((error) => {
178
+ if (isErrorNativeBehavior || !_Aborter.isError(error) || hasThrowInTimeoutError(error)) {
179
+ this.setRequestState("rejected");
35
180
  return reject(error);
36
181
  }
37
182
  promise = null;
@@ -39,17 +184,41 @@ const _Aborter = class _Aborter {
39
184
  });
40
185
  return promise;
41
186
  };
42
- this.abort = (reason) => this.abortController.abort(reason);
187
+ this.abort = (reason) => {
188
+ if (!this.isRequestInProgress) return;
189
+ this.abortController.abort(reason);
190
+ this.setRequestState("aborted");
191
+ const error = getAbortErrorByReason(reason);
192
+ if (error && error.type) {
193
+ this.listeners.dispatchEvent(error.type, error);
194
+ } else {
195
+ this.listeners.dispatchEvent(
196
+ "aborted",
197
+ new AbortError(ErrorMessage.AbortedSignalWithoutMessage, { initiator: "user" })
198
+ );
199
+ }
200
+ };
201
+ this.abortWithRecovery = (reason) => {
202
+ this.abort(reason);
203
+ this.abortController = new AbortController();
204
+ return this.abortController;
205
+ };
206
+ this.dispose = () => {
207
+ this.timeout.clearTimeout();
208
+ clearEventListeners(this.listeners);
209
+ };
210
+ this.listeners = new EventListener(options);
43
211
  }
44
212
  /**
45
213
  * Returns the AbortSignal object associated with this object.
46
214
  */
47
215
  get signal() {
48
- return this.abortController.signal;
216
+ return this.abortController?.signal;
49
217
  }
50
218
  };
51
219
  _Aborter.errorName = ABORT_ERROR_NAME;
52
220
  _Aborter.isError = isError;
53
221
  let Aborter = _Aborter;
222
+ exports.AbortError = AbortError;
54
223
  exports.Aborter = Aborter;
55
- //# sourceMappingURL=index.cjs.js.map
224
+ exports.TimeoutError = TimeoutError;
package/dist/index.d.ts CHANGED
@@ -1,46 +1,281 @@
1
1
  export declare class Aborter {
2
2
  protected abortController: AbortController;
3
+ protected isRequestInProgress: boolean;
4
+ protected timeout: Timeout;
5
+ /**
6
+ * Returns an `EventListener` instance to listen for `Aborter` events.
7
+ */
8
+ listeners: EventListener_2;
9
+ constructor(options?: Types_4.AborterOptions);
3
10
  /**
4
11
  * The name of the error instance thrown by the AbortSignal.
5
12
  * @readonly
13
+ * @deprecated use AbortError.name
6
14
  */
7
15
  static readonly errorName = "AbortError";
8
16
  /**
9
17
  * Method of checking whether an error is an error AbortError.
10
18
  * @returns boolean
11
19
  */
12
- static isError: (error: unknown) => error is Error;
20
+ static isError: (error: any) => error is Error;
13
21
  /**
14
22
  * Returns the AbortSignal object associated with this object.
15
23
  */
16
24
  get signal(): AbortSignal;
25
+ private setRequestState;
17
26
  /**
18
27
  * Performs an asynchronous request with cancellation of the previous request, preventing the call of the catch block when the request is canceled and the subsequent finally block.
19
28
  * @param request callback function
20
29
  * @param options an object that receives a set of settings for performing a request attempt
21
30
  * @returns Promise
22
31
  */
23
- try: <R>(request: Types.AbortRequest<R>, { isErrorNativeBehavior }?: Types.FnTryOptions) => Promise<R>;
32
+ try: <R>(request: Types_4.AbortRequest<R>, { isErrorNativeBehavior, timeout }?: Types_4.FnTryOptions) => Promise<R>;
24
33
  /**
25
34
  * Calling this method sets the AbortSignal flag of this object and signals all observers that the associated action should be aborted.
26
35
  */
27
36
  abort: (reason?: any) => void;
37
+ /**
38
+ * Calling this method sets the AbortSignal flag of this object and signals all observers that the associated action should be aborted.
39
+ * After aborting, it restores the AbortSignal, resetting the isAborted property, and interaction with the signal property becomes available again.
40
+ */
41
+ abortWithRecovery: (reason?: any) => AbortController;
42
+ /**
43
+ * Clears the object's data completely: all subscriptions in all properties, clears overridden methods, state values.
44
+ */
45
+ dispose: () => void;
28
46
  }
29
47
 
48
+ declare interface AborterOptions extends Pick<EventListenerOptions_2, 'onAbort' | 'onStateChange'> {
49
+ }
50
+
51
+ export declare class AbortError<Options extends Types.AbortErrorOptions = {}> extends Error {
52
+ /**
53
+ * Interrupt error code.
54
+ * @readonly
55
+ */
56
+ readonly code: number;
57
+ /**
58
+ * Interrupt type 'cancelled' | 'aborted'.
59
+ * @default `aborted`
60
+ */
61
+ type: Types.AbortErrorOptions['type'];
62
+ /**
63
+ * The timestamp in milliseconds when the error was created.
64
+ @readonly
65
+ @returns Date.now();
66
+ */
67
+ readonly timestamp: number;
68
+ /**
69
+ * Additional reason or data associated with the interrupt.
70
+ */
71
+ reason?: any;
72
+ /**
73
+ * AbortSignal that was just interrupted.
74
+ */
75
+ signal?: AbortSignal;
76
+ /**
77
+ * A field containing additional error information indicating the reason for the current error.
78
+ */
79
+ cause?: Options['initiator'] extends 'timeout' ? TimeoutError : Error;
80
+ /**
81
+ * field with the name of the error initiator.
82
+ */
83
+ initiator?: Types.AbortInitiator;
84
+ constructor(message: string, options?: Options);
85
+ }
86
+
87
+ declare interface AbortErrorOptions {
88
+ type?: AbortType;
89
+ reason?: any;
90
+ cause?: any;
91
+ signal?: AbortSignal;
92
+ initiator?: AbortInitiator;
93
+ }
94
+
95
+ export declare type AbortInitiator = 'timeout' | 'user' | 'system' | {};
96
+
30
97
  declare type AbortRequest<T> = (signal: AbortSignal) => Promise<T>;
31
98
 
99
+ export declare type AbortType = 'cancelled' | 'aborted';
100
+
101
+ declare const CLEAR_METHOD_SYMBOL: unique symbol;
102
+
103
+ declare const CLEAR_METHOD_SYMBOL_2: unique symbol;
104
+
105
+ declare namespace Constants {
106
+ export {
107
+ EMIT_METHOD_SYMBOL,
108
+ CLEAR_METHOD_SYMBOL
109
+ }
110
+ }
111
+
112
+ declare const EMIT_METHOD_SYMBOL: unique symbol;
113
+
114
+ declare type EventCallback<T extends EventListenerType> = EventMap[T] extends undefined ? () => void : (event: EventMap[T]) => void;
115
+
116
+ declare class EventListener_2 {
117
+ private listeners;
118
+ /**
119
+ * Method called when an Aborter request is cancelled
120
+ */
121
+ onabort?: Types_2.OnAbortCallback;
122
+ /**
123
+ * Returns an `StateObserver` object for monitoring the status of requests.
124
+ */
125
+ state: StateObserver;
126
+ constructor(options?: Types_2.EventListenerOptions);
127
+ private getListenersByType;
128
+ /**
129
+ * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
130
+ */
131
+ addEventListener: <T extends Types_2.EventListenerType, L extends Types_2.EventCallback<T>>(type: T, listener: L) => VoidFunction;
132
+ /**
133
+ * Removes the event listener in target's event listener list with the same type and callback.
134
+ */
135
+ removeEventListener: <T extends Types_2.EventListenerType, L extends Types_2.EventCallback<T>>(type: T, listener: L) => void;
136
+ /**
137
+ * Dispatches a synthetic event event to target
138
+ */
139
+ dispatchEvent: <T extends Types_2.EventListenerType, E extends Types_2.EventMap[T]>(type: T, event: E) => void;
140
+ /* Excluded from this release type: [CLEAR_METHOD_SYMBOL] */
141
+ }
142
+
143
+ declare interface EventListenerOptions_2 {
144
+ onAbort?: OnAbortCallback;
145
+ onStateChange?: OnStateChangeCallback;
146
+ }
147
+
148
+ declare type EventListenerType = keyof EventMap;
149
+
150
+ declare interface EventMap {
151
+ aborted: AbortError;
152
+ cancelled: AbortError;
153
+ }
154
+
32
155
  declare interface FnTryOptions {
33
156
  /**
34
157
  * Returns the ability to catch a canceled request error in a catch block.
35
158
  * @default false
36
159
  */
37
160
  isErrorNativeBehavior?: boolean;
161
+ /**
162
+ * Automatic request cancellation setting field.
163
+ */
164
+ timeout?: TimeoutErrorOptions;
165
+ }
166
+
167
+ export declare type OnAbortCallback = (error: AbortError) => void;
168
+
169
+ export declare type OnStateChangeCallback = (state: RequestState) => void;
170
+
171
+ export declare type RequestState = 'pending' | 'fulfilled' | 'rejected' | 'cancelled' | 'aborted';
172
+
173
+ declare interface StateListenerOptions {
174
+ /**
175
+ * Callback triggered on state change.
176
+ */
177
+ onStateChange?: OnStateChangeCallback;
178
+ }
179
+
180
+ declare class StateObserver {
181
+ /**
182
+ * Current state value.
183
+ */
184
+ value?: Types_3.RequestState;
185
+ /**
186
+ * Callback triggered on state change.
187
+ */
188
+ onstatechange?: Types_3.OnStateChangeCallback;
189
+ private subscribers;
190
+ constructor(options?: Types_3.StateListenerOptions);
191
+ /**
192
+ * Subscribes a callback to state changes.
193
+ * @param callbackfn - Callback function
194
+ * @returns {VoidFunction} Unsubscribe function
195
+ */
196
+ subscribe: (callbackfn: Types_3.OnStateChangeCallback) => VoidFunction;
197
+ /**
198
+ * Unsubscribes a callback from state changes.
199
+ * @param callbackfn - Callback function to remove
200
+ * @returns {void}
201
+ */
202
+ unsubscribe: (callbackfn: Types_3.OnStateChangeCallback) => void;
203
+ /* Excluded from this release type: [Constants.EMIT_METHOD_SYMBOL] */
204
+ /* Excluded from this release type: [Constants.CLEAR_METHOD_SYMBOL] */
205
+ }
206
+
207
+ declare class Timeout {
208
+ private timeoutId?;
209
+ /**
210
+ * Sets the timeout for the current request.
211
+ */
212
+ setTimeout: (timeout: number | undefined, onAbort: VoidFunction) => void;
213
+ /**
214
+ * Clears the set timeout.
215
+ */
216
+ clearTimeout: () => void;
217
+ }
218
+
219
+ export declare class TimeoutError extends Error {
220
+ /**
221
+ *The timestamp in milliseconds when the error was created.
222
+ @readonly
223
+ @returns Date.now();
224
+ */
225
+ readonly timestamp: number;
226
+ /**
227
+ * A field indicating whether an error was thrown.
228
+ */
229
+ hasThrow: boolean;
230
+ /**
231
+ * A field displaying the time in milliseconds after which the request was interrupted.
232
+ */
233
+ ms?: number;
234
+ constructor(message: string, options?: TimeoutErrorOptions);
235
+ }
236
+
237
+ declare interface TimeoutErrorOptions {
238
+ /**
239
+ * A flag that determines whether to throw the error further.
240
+ */
241
+ hasThrow?: boolean;
242
+ /**
243
+ * Time in milliseconds after which interrupts should be started.
244
+ */
245
+ ms: number;
38
246
  }
39
247
 
40
248
  declare namespace Types {
249
+ export {
250
+ AbortInitiator,
251
+ AbortType,
252
+ AbortErrorOptions
253
+ }
254
+ }
255
+
256
+ declare namespace Types_2 {
257
+ export {
258
+ EventMap,
259
+ EventListenerType,
260
+ EventCallback,
261
+ OnAbortCallback,
262
+ EventListenerOptions_2 as EventListenerOptions
263
+ }
264
+ }
265
+
266
+ declare namespace Types_3 {
267
+ export {
268
+ RequestState,
269
+ OnStateChangeCallback,
270
+ StateListenerOptions
271
+ }
272
+ }
273
+
274
+ declare namespace Types_4 {
41
275
  export {
42
276
  AbortRequest,
43
- FnTryOptions
277
+ FnTryOptions,
278
+ AborterOptions
44
279
  }
45
280
  }
46
281