@handy-common-utils/promise-utils 1.0.6 → 1.2.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/README.md +7 -1
- package/dist/promise-utils.d.ts +48 -18
- package/dist/promise-utils.d.ts.map +1 -1
- package/dist/promise-utils.js +91 -23
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @handy-common-utils/promise-utils
|
|
2
2
|
|
|
3
|
-
Promise related utilities
|
|
3
|
+
These Promise related utilities have 100% test coverage. The package is tiny because there is no dependency on any other package.
|
|
4
|
+
Functions provided are `repeat`, `withRetry`, `inParallel`, `delayedResolve`, `delayedReject`, `timoutResolve`, `timeoutReject`, `promiseState`, `synchronized`, etc.
|
|
5
|
+
|
|
6
|
+
[](https://npmjs.org/package/@handy-common-utils/promise-utils)
|
|
7
|
+
[](https://npmjs.org/package/@handy-common-utils/promise-utils)
|
|
8
|
+
[](https://github.com/handy-common-utils/promise-utils/actions/workflows/ci.yml)
|
|
9
|
+
[](https://codecov.io/gh/handy-common-utils/promise-utils)
|
|
4
10
|
|
|
5
11
|
## How to use
|
|
6
12
|
|
package/dist/promise-utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare
|
|
1
|
+
export declare const FIBONACCI_SEQUENCE: number[];
|
|
2
2
|
export declare enum PromiseState {
|
|
3
3
|
Pending = "Pending",
|
|
4
4
|
Fulfilled = "Fulfilled",
|
|
@@ -10,7 +10,7 @@ export declare abstract class PromiseUtils {
|
|
|
10
10
|
* This function is useful for client side pagination.
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
|
-
* const domainNameObjects = await
|
|
13
|
+
* const domainNameObjects = await PromiseUtils.repeat(
|
|
14
14
|
* pagingParam => apig.getDomainNames({limit: 500, ...pagingParam}).promise(),
|
|
15
15
|
* esponse => response.position? {position: response.position} : null,
|
|
16
16
|
* (collection, response) => collection.concat(response.items!),
|
|
@@ -25,19 +25,43 @@ export declare abstract class PromiseUtils {
|
|
|
25
25
|
* @param nextParameter The function for calculating next parameter from the operation result.
|
|
26
26
|
* Normally the parameter controls paging,
|
|
27
27
|
* This function should return null when next invocation of the operation function is not desired.
|
|
28
|
+
* If next invocation is desired, the return value of this function can be a Promise or not a Promise.
|
|
28
29
|
* @param collect the function for merging operation result into the collection
|
|
29
30
|
* @param initialCollection initial collection which would be the first argument passed into the first invocation of the collect function
|
|
30
31
|
* @param initialParameter the parameter for the first operation
|
|
31
32
|
* @returns Promise of collection of all the results returned by the operation function
|
|
32
33
|
*
|
|
33
34
|
*/
|
|
34
|
-
static repeat<Result, Param, Collection>(operation: (parameter: Partial<Param>) => Promise<Result>, nextParameter: (response: Result) => Partial<Param> | null, collect: (collection: Collection, result: Result) => Collection, initialCollection: Collection, initialParameter?: Partial<Param>): Promise<Collection>;
|
|
35
|
+
static repeat<Result, Param, Collection>(operation: (parameter: Partial<Param>) => Promise<Result>, nextParameter: (response: Result) => Partial<Param> | Promise<Partial<Param>> | null, collect: (collection: Collection, result: Result) => Collection, initialCollection: Collection, initialParameter?: Partial<Param>): Promise<Collection>;
|
|
36
|
+
/**
|
|
37
|
+
* Do an operation repeatedly until a criteria is met.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const result = await PromiseUtils.withRetry(() => doSomething(), [100, 200, 300, 500, 800, 1000]);
|
|
41
|
+
*
|
|
42
|
+
* @template Result type of the operation result
|
|
43
|
+
* @template TError type of the possible error that could be generated by the operation
|
|
44
|
+
*
|
|
45
|
+
* @param operation a function that outputs a Promise result, normally the operation does not use its arguments
|
|
46
|
+
* @param backoff Array of retry backoff periods (unit: milliseconds) or function for calculating them.
|
|
47
|
+
* If retry is desired, before making next call to the operation the desired backoff period would be waited.
|
|
48
|
+
* If the array runs out of elements or the function returns `undefined` or either the array or the function returns a negative number,
|
|
49
|
+
* there would be no further call to the operation.
|
|
50
|
+
* The `attempt` argument passed into backoff function starts from 2 because only retries need to backoff,
|
|
51
|
+
* so the first retry is the second attempt.
|
|
52
|
+
* @param shouldRetry Predicate function for deciding whether another call to the operation should happen.
|
|
53
|
+
* If this argument is not defined, retry would happen whenever the operation rejects with an error.
|
|
54
|
+
* `shouldRetry` would be evaluated before `backoff`.
|
|
55
|
+
* The `attempt` argument passed into shouldRetry function starts from 1.
|
|
56
|
+
* @returns Promise of the operation result potentially with retries already applied
|
|
57
|
+
*/
|
|
58
|
+
static withRetry<Result, TError = any>(operation: (attempt: number, previousResult: Result | undefined, previousError: TError | undefined) => Promise<Result>, backoff: Array<number> | ((attempt: number, previousResult: Result | undefined, previousError: TError | undefined) => number | undefined), shouldRetry?: (previousError: TError | undefined, previousResult: Result | undefined, attempt: number) => boolean): Promise<Result>;
|
|
35
59
|
/**
|
|
36
60
|
* Run multiple jobs/operations in parallel.
|
|
37
61
|
*
|
|
38
62
|
* @example
|
|
39
63
|
* const topicArns = topics.map(topic => topic.TopicArn!);
|
|
40
|
-
* await
|
|
64
|
+
* await PromiseUtils.inParallel(5, topicArns, async topicArn => {
|
|
41
65
|
* const topicAttributes = (await sns.getTopicAttributes({ TopicArn: topicArn }).promise()).Attributes!;
|
|
42
66
|
* const topicDetails = { ...topicAttributes, subscriptions: [] } as any;
|
|
43
67
|
* if (this.shouldInclude(topicArn)) {
|
|
@@ -53,41 +77,45 @@ export declare abstract class PromiseUtils {
|
|
|
53
77
|
* This function is safe when there are infinite unknown number of elements in the job data.
|
|
54
78
|
* @param operation the function that turns job data into result asynchronously
|
|
55
79
|
* @returns Promise of void if the operation function does not return a value,
|
|
56
|
-
* or promise of an
|
|
80
|
+
* or promise of an array containing results returned from the operation function.
|
|
81
|
+
* In the array containing results, each element is either the fulfilled result, or the rejected error/reason.
|
|
57
82
|
*/
|
|
58
|
-
static inParallel<Data, Result>(parallelism: number, jobs: Iterable<Data>, operation: (job: Data, index: number) => Promise<Result>): Promise<
|
|
83
|
+
static inParallel<Data, Result, TError = Result>(parallelism: number, jobs: Iterable<Data>, operation: (job: Data, index: number) => Promise<Result>): Promise<Array<Result | TError>>;
|
|
59
84
|
/**
|
|
60
85
|
* Create a Promise that resolves after number of milliseconds specified
|
|
61
86
|
* @param ms number of milliseconds after which the created Promise would resolve
|
|
62
|
-
* @param result the result to be resolved for the Promise
|
|
87
|
+
* @param result the result to be resolved for the Promise, or a function that supplies the reuslt.
|
|
63
88
|
* @returns the new Promise created
|
|
64
89
|
*/
|
|
65
|
-
static delayedResolve<T>(ms: number, result?: T | PromiseLike<T> |
|
|
90
|
+
static delayedResolve<T>(ms: number, result?: T | PromiseLike<T> | (() => (T | PromiseLike<T>))): Promise<T>;
|
|
66
91
|
/**
|
|
67
|
-
* Create a Promise that rejects after number of milliseconds specified
|
|
92
|
+
* Create a Promise that rejects after number of milliseconds specified.
|
|
68
93
|
* @param ms number of milliseconds after which the created Promise would reject
|
|
69
|
-
* @param reason the reason of the rejection for the Promise
|
|
94
|
+
* @param reason the reason of the rejection for the Promise, or a function that supplies the reason.
|
|
95
|
+
* If the reason ends up to be a rejected Promise, then the outcome (could be fulfilled or rejected) of it will be the reject reason of the Promise returned.
|
|
70
96
|
* @returns the new Promise created
|
|
71
97
|
*/
|
|
72
|
-
static delayedReject<T = never>(ms: number, reason:
|
|
98
|
+
static delayedReject<T = never, R = any>(ms: number, reason: R | PromiseLike<R> | (() => R | PromiseLike<R>)): Promise<T>;
|
|
73
99
|
/**
|
|
74
100
|
* Apply timeout to an operation, in case timeout happens, resolve to the result specified.
|
|
75
101
|
* If timeout does not happen, the resolved result or rejection reason of the original operation would be returned.
|
|
102
|
+
* If timeout does not happen and result is a function, the function won't be called.
|
|
76
103
|
* @param operation the original operation that timeout would be applied
|
|
77
104
|
* @param ms number of milliseconds for the timeout
|
|
78
|
-
* @param result the result to be resolved in case timeout happens
|
|
105
|
+
* @param result the result to be resolved in case timeout happens, or a function that supplies the reuslt.
|
|
79
106
|
* @return the new Promise that resolves to the specified result in case timeout happens
|
|
80
107
|
*/
|
|
81
|
-
static timeoutResolve<T>(operation: Promise<T>, ms: number, result?: T | PromiseLike<T> | undefined): Promise<T>;
|
|
108
|
+
static timeoutResolve<T>(operation: Promise<T>, ms: number, result?: T | PromiseLike<T> | (() => (T | PromiseLike<T>)) | undefined): Promise<T>;
|
|
82
109
|
/**
|
|
83
110
|
* Apply timeout to an operation, in case timeout happens, reject with the reason specified.
|
|
84
111
|
* If timeout does not happen, the resolved result or rejection reason of the original operation would be returned.
|
|
112
|
+
* If timeout does not happen and rejectReason is a function, the function won't be called.
|
|
85
113
|
* @param operation the original operation that timeout would be applied
|
|
86
114
|
* @param ms number of milliseconds for the timeout
|
|
87
|
-
* @param rejectReason the reason of the rejection in case timeout happens
|
|
115
|
+
* @param rejectReason the reason of the rejection in case timeout happens, or a function that supplies the reason.
|
|
88
116
|
* @return the new Promise that rejects with the specified reason in case timeout happens
|
|
89
117
|
*/
|
|
90
|
-
static timeoutReject<T>(operation: Promise<T>, ms: number, rejectReason:
|
|
118
|
+
static timeoutReject<T = never, R = any>(operation: Promise<T>, ms: number, rejectReason: R | PromiseLike<R> | (() => R | PromiseLike<R>)): Promise<T>;
|
|
91
119
|
/**
|
|
92
120
|
* Get the state of the Promise.
|
|
93
121
|
* Please note that the returned value is a Promise, although it resolves immediately.
|
|
@@ -99,21 +127,23 @@ export declare abstract class PromiseUtils {
|
|
|
99
127
|
/**
|
|
100
128
|
* Equivalent of `synchronized` in Java.
|
|
101
129
|
* In any situation there's no concurrent execution of any operation function associated with the same lock.
|
|
102
|
-
* The operation function has access to the state (when `synchronized` is called), settledState (when the operation function is called),
|
|
130
|
+
* The operation function has access to the state (when `synchronized` is called), settledState (when the operation function is called),
|
|
131
|
+
* and result (could be the fulfilled result or the rejected reason) of the previous operation.
|
|
103
132
|
* In case there is no previous invocation, state, settledState and result would all be undefined.
|
|
104
133
|
* @param lock the object (could be a string, a number, or `this` in a class) that is used to apply the lock
|
|
105
134
|
* @param operation function for doing the computation and returning a Promise
|
|
106
135
|
* @returns the result of the operation function
|
|
107
136
|
*/
|
|
108
|
-
static synchronized<T>(lock:
|
|
137
|
+
static synchronized<T>(lock: unknown, operation: (previousState: PromiseState | undefined, previousSettledState: PromiseState | undefined, previousResult: any) => Promise<T>): Promise<T>;
|
|
109
138
|
}
|
|
110
139
|
export declare const repeat: typeof PromiseUtils.repeat;
|
|
140
|
+
export declare const withRetry: typeof PromiseUtils.withRetry;
|
|
111
141
|
export declare const inParallel: typeof PromiseUtils.inParallel;
|
|
112
142
|
export declare const delayedResolve: typeof PromiseUtils.delayedResolve;
|
|
113
143
|
export declare const delayedReject: typeof PromiseUtils.delayedReject;
|
|
114
144
|
export declare const timeoutResolve: typeof PromiseUtils.timeoutResolve;
|
|
115
145
|
export declare const timeoutReject: typeof PromiseUtils.timeoutReject;
|
|
116
146
|
export declare const synchronized: typeof PromiseUtils.synchronized;
|
|
147
|
+
export declare const synchronised: typeof PromiseUtils.synchronized;
|
|
117
148
|
export declare const promiseState: typeof PromiseUtils.promiseState;
|
|
118
|
-
export {};
|
|
119
149
|
//# sourceMappingURL=promise-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-utils.d.ts","sourceRoot":"","sources":["../src/promise-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"promise-utils.d.ts","sourceRoot":"","sources":["../src/promise-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/promise-utils.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable unicorn/no-null */
|
|
3
|
+
/* eslint-disable no-await-in-loop */
|
|
2
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.promiseState = exports.synchronized = exports.timeoutReject = exports.timeoutResolve = exports.delayedReject = exports.delayedResolve = exports.inParallel = exports.repeat = exports.PromiseUtils = exports.PromiseState = void 0;
|
|
5
|
+
exports.promiseState = exports.synchronised = exports.synchronized = exports.timeoutReject = exports.timeoutResolve = exports.delayedReject = exports.delayedResolve = exports.inParallel = exports.withRetry = exports.repeat = exports.PromiseUtils = exports.PromiseState = exports.FIBONACCI_SEQUENCE = void 0;
|
|
6
|
+
exports.FIBONACCI_SEQUENCE = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811];
|
|
4
7
|
var PromiseState;
|
|
5
8
|
(function (PromiseState) {
|
|
6
9
|
PromiseState["Pending"] = "Pending";
|
|
@@ -13,7 +16,7 @@ class PromiseUtils {
|
|
|
13
16
|
* This function is useful for client side pagination.
|
|
14
17
|
*
|
|
15
18
|
* @example
|
|
16
|
-
* const domainNameObjects = await
|
|
19
|
+
* const domainNameObjects = await PromiseUtils.repeat(
|
|
17
20
|
* pagingParam => apig.getDomainNames({limit: 500, ...pagingParam}).promise(),
|
|
18
21
|
* esponse => response.position? {position: response.position} : null,
|
|
19
22
|
* (collection, response) => collection.concat(response.items!),
|
|
@@ -28,6 +31,7 @@ class PromiseUtils {
|
|
|
28
31
|
* @param nextParameter The function for calculating next parameter from the operation result.
|
|
29
32
|
* Normally the parameter controls paging,
|
|
30
33
|
* This function should return null when next invocation of the operation function is not desired.
|
|
34
|
+
* If next invocation is desired, the return value of this function can be a Promise or not a Promise.
|
|
31
35
|
* @param collect the function for merging operation result into the collection
|
|
32
36
|
* @param initialCollection initial collection which would be the first argument passed into the first invocation of the collect function
|
|
33
37
|
* @param initialParameter the parameter for the first operation
|
|
@@ -42,16 +46,60 @@ class PromiseUtils {
|
|
|
42
46
|
// eslint-disable-next-line no-await-in-loop
|
|
43
47
|
const result = await operation(param);
|
|
44
48
|
collection = collect(collection, result);
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
const paramOrPromise = nextParameter(result);
|
|
50
|
+
if (paramOrPromise === null) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
param = await paramOrPromise;
|
|
54
|
+
} while (true);
|
|
47
55
|
return collection;
|
|
48
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Do an operation repeatedly until a criteria is met.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const result = await PromiseUtils.withRetry(() => doSomething(), [100, 200, 300, 500, 800, 1000]);
|
|
62
|
+
*
|
|
63
|
+
* @template Result type of the operation result
|
|
64
|
+
* @template TError type of the possible error that could be generated by the operation
|
|
65
|
+
*
|
|
66
|
+
* @param operation a function that outputs a Promise result, normally the operation does not use its arguments
|
|
67
|
+
* @param backoff Array of retry backoff periods (unit: milliseconds) or function for calculating them.
|
|
68
|
+
* If retry is desired, before making next call to the operation the desired backoff period would be waited.
|
|
69
|
+
* If the array runs out of elements or the function returns `undefined` or either the array or the function returns a negative number,
|
|
70
|
+
* there would be no further call to the operation.
|
|
71
|
+
* The `attempt` argument passed into backoff function starts from 2 because only retries need to backoff,
|
|
72
|
+
* so the first retry is the second attempt.
|
|
73
|
+
* @param shouldRetry Predicate function for deciding whether another call to the operation should happen.
|
|
74
|
+
* If this argument is not defined, retry would happen whenever the operation rejects with an error.
|
|
75
|
+
* `shouldRetry` would be evaluated before `backoff`.
|
|
76
|
+
* The `attempt` argument passed into shouldRetry function starts from 1.
|
|
77
|
+
* @returns Promise of the operation result potentially with retries already applied
|
|
78
|
+
*/
|
|
79
|
+
static async withRetry(operation, backoff, shouldRetry = (previousError, _previousResult, _attempt) => previousError !== undefined) {
|
|
80
|
+
let attempt = 1;
|
|
81
|
+
const finalOutcome = await PromiseUtils.repeat((previousOutcome) => operation(attempt, previousOutcome.result, previousOutcome.error).then(result => ({ result })).catch(error => ({ error })), outcome => {
|
|
82
|
+
if (!shouldRetry(outcome.error, outcome.result, attempt)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const backoffMs = Array.isArray(backoff) ? backoff[attempt - 1] : backoff(attempt, outcome.result, outcome.error);
|
|
86
|
+
if (backoffMs == null || backoffMs < 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
attempt++;
|
|
90
|
+
return PromiseUtils.delayedResolve(backoffMs, outcome);
|
|
91
|
+
}, (_, outcome) => outcome, {});
|
|
92
|
+
if (finalOutcome.error !== undefined) {
|
|
93
|
+
throw finalOutcome.error;
|
|
94
|
+
}
|
|
95
|
+
return finalOutcome.result;
|
|
96
|
+
}
|
|
49
97
|
/**
|
|
50
98
|
* Run multiple jobs/operations in parallel.
|
|
51
99
|
*
|
|
52
100
|
* @example
|
|
53
101
|
* const topicArns = topics.map(topic => topic.TopicArn!);
|
|
54
|
-
* await
|
|
102
|
+
* await PromiseUtils.inParallel(5, topicArns, async topicArn => {
|
|
55
103
|
* const topicAttributes = (await sns.getTopicAttributes({ TopicArn: topicArn }).promise()).Attributes!;
|
|
56
104
|
* const topicDetails = { ...topicAttributes, subscriptions: [] } as any;
|
|
57
105
|
* if (this.shouldInclude(topicArn)) {
|
|
@@ -67,7 +115,8 @@ class PromiseUtils {
|
|
|
67
115
|
* This function is safe when there are infinite unknown number of elements in the job data.
|
|
68
116
|
* @param operation the function that turns job data into result asynchronously
|
|
69
117
|
* @returns Promise of void if the operation function does not return a value,
|
|
70
|
-
* or promise of an
|
|
118
|
+
* or promise of an array containing results returned from the operation function.
|
|
119
|
+
* In the array containing results, each element is either the fulfilled result, or the rejected error/reason.
|
|
71
120
|
*/
|
|
72
121
|
static async inParallel(parallelism, jobs, operation) {
|
|
73
122
|
if (parallelism < 1) {
|
|
@@ -76,7 +125,7 @@ class PromiseUtils {
|
|
|
76
125
|
const jobResults = new Array();
|
|
77
126
|
let index = 0;
|
|
78
127
|
const iterator = jobs[Symbol.iterator]();
|
|
79
|
-
const promises =
|
|
128
|
+
const promises = Array.from({ length: Math.floor(parallelism) }).fill(0).map(async (_) => {
|
|
80
129
|
let iteratorResult;
|
|
81
130
|
while (true) {
|
|
82
131
|
iteratorResult = iterator.next();
|
|
@@ -86,54 +135,69 @@ class PromiseUtils {
|
|
|
86
135
|
const job = iteratorResult.value;
|
|
87
136
|
const jobIndex = index++;
|
|
88
137
|
const jobResultPromise = operation(job, jobIndex);
|
|
89
|
-
|
|
90
|
-
if (jobResult !== undefined) {
|
|
91
|
-
jobResults[jobIndex] = jobResult;
|
|
92
|
-
}
|
|
138
|
+
jobResults[jobIndex] = await jobResultPromise.catch(error => error);
|
|
93
139
|
}
|
|
94
140
|
});
|
|
95
141
|
await Promise.all(promises);
|
|
96
|
-
return
|
|
142
|
+
return jobResults;
|
|
97
143
|
}
|
|
98
144
|
/**
|
|
99
145
|
* Create a Promise that resolves after number of milliseconds specified
|
|
100
146
|
* @param ms number of milliseconds after which the created Promise would resolve
|
|
101
|
-
* @param result the result to be resolved for the Promise
|
|
147
|
+
* @param result the result to be resolved for the Promise, or a function that supplies the reuslt.
|
|
102
148
|
* @returns the new Promise created
|
|
103
149
|
*/
|
|
104
150
|
static delayedResolve(ms, result) {
|
|
105
|
-
return new Promise(resolve => setTimeout(() => resolve(result), ms));
|
|
151
|
+
return new Promise(resolve => setTimeout(() => resolve(typeof result === 'function' ? result() : result), ms));
|
|
106
152
|
}
|
|
107
153
|
/**
|
|
108
|
-
* Create a Promise that rejects after number of milliseconds specified
|
|
154
|
+
* Create a Promise that rejects after number of milliseconds specified.
|
|
109
155
|
* @param ms number of milliseconds after which the created Promise would reject
|
|
110
|
-
* @param reason the reason of the rejection for the Promise
|
|
156
|
+
* @param reason the reason of the rejection for the Promise, or a function that supplies the reason.
|
|
157
|
+
* If the reason ends up to be a rejected Promise, then the outcome (could be fulfilled or rejected) of it will be the reject reason of the Promise returned.
|
|
111
158
|
* @returns the new Promise created
|
|
112
159
|
*/
|
|
113
160
|
static delayedReject(ms, reason) {
|
|
114
|
-
return new Promise((_resolve, reject) => setTimeout(() =>
|
|
161
|
+
return new Promise((_resolve, reject) => setTimeout(() => {
|
|
162
|
+
const r = typeof reason === 'function' ? reason() : reason;
|
|
163
|
+
Promise.resolve(r).catch(error => error).then(r => reject(r));
|
|
164
|
+
}, ms));
|
|
115
165
|
}
|
|
116
166
|
/**
|
|
117
167
|
* Apply timeout to an operation, in case timeout happens, resolve to the result specified.
|
|
118
168
|
* If timeout does not happen, the resolved result or rejection reason of the original operation would be returned.
|
|
169
|
+
* If timeout does not happen and result is a function, the function won't be called.
|
|
119
170
|
* @param operation the original operation that timeout would be applied
|
|
120
171
|
* @param ms number of milliseconds for the timeout
|
|
121
|
-
* @param result the result to be resolved in case timeout happens
|
|
172
|
+
* @param result the result to be resolved in case timeout happens, or a function that supplies the reuslt.
|
|
122
173
|
* @return the new Promise that resolves to the specified result in case timeout happens
|
|
123
174
|
*/
|
|
124
175
|
static timeoutResolve(operation, ms, result) {
|
|
125
|
-
return Promise.race([
|
|
176
|
+
return Promise.race([
|
|
177
|
+
operation,
|
|
178
|
+
PromiseUtils.delayedResolve(ms, () => PromiseUtils.promiseState(operation)
|
|
179
|
+
.then(state => state === PromiseState.Pending ?
|
|
180
|
+
(typeof result === 'function' ? result() : result) :
|
|
181
|
+
{})),
|
|
182
|
+
]);
|
|
126
183
|
}
|
|
127
184
|
/**
|
|
128
185
|
* Apply timeout to an operation, in case timeout happens, reject with the reason specified.
|
|
129
186
|
* If timeout does not happen, the resolved result or rejection reason of the original operation would be returned.
|
|
187
|
+
* If timeout does not happen and rejectReason is a function, the function won't be called.
|
|
130
188
|
* @param operation the original operation that timeout would be applied
|
|
131
189
|
* @param ms number of milliseconds for the timeout
|
|
132
|
-
* @param rejectReason the reason of the rejection in case timeout happens
|
|
190
|
+
* @param rejectReason the reason of the rejection in case timeout happens, or a function that supplies the reason.
|
|
133
191
|
* @return the new Promise that rejects with the specified reason in case timeout happens
|
|
134
192
|
*/
|
|
135
193
|
static timeoutReject(operation, ms, rejectReason) {
|
|
136
|
-
return Promise.race([
|
|
194
|
+
return Promise.race([
|
|
195
|
+
operation,
|
|
196
|
+
PromiseUtils.delayedReject(ms, () => PromiseUtils.promiseState(operation)
|
|
197
|
+
.then(state => state === PromiseState.Pending ?
|
|
198
|
+
(typeof rejectReason === 'function' ? rejectReason() : rejectReason) :
|
|
199
|
+
{})),
|
|
200
|
+
]);
|
|
137
201
|
}
|
|
138
202
|
/**
|
|
139
203
|
* Get the state of the Promise.
|
|
@@ -149,7 +213,8 @@ class PromiseUtils {
|
|
|
149
213
|
/**
|
|
150
214
|
* Equivalent of `synchronized` in Java.
|
|
151
215
|
* In any situation there's no concurrent execution of any operation function associated with the same lock.
|
|
152
|
-
* The operation function has access to the state (when `synchronized` is called), settledState (when the operation function is called),
|
|
216
|
+
* The operation function has access to the state (when `synchronized` is called), settledState (when the operation function is called),
|
|
217
|
+
* and result (could be the fulfilled result or the rejected reason) of the previous operation.
|
|
153
218
|
* In case there is no previous invocation, state, settledState and result would all be undefined.
|
|
154
219
|
* @param lock the object (could be a string, a number, or `this` in a class) that is used to apply the lock
|
|
155
220
|
* @param operation function for doing the computation and returning a Promise
|
|
@@ -164,9 +229,10 @@ class PromiseUtils {
|
|
|
164
229
|
}
|
|
165
230
|
switch (previousState) {
|
|
166
231
|
case PromiseState.Pending: // concurrency
|
|
167
|
-
resultPromise = previousResultPromise.then(result => operation(PromiseState.Pending, PromiseState.Fulfilled, result),
|
|
232
|
+
resultPromise = previousResultPromise.then(result => operation(PromiseState.Pending, PromiseState.Fulfilled, result), error => operation(PromiseState.Pending, PromiseState.Rejected, error));
|
|
168
233
|
break;
|
|
169
234
|
case undefined: // no concurrency and no history
|
|
235
|
+
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
170
236
|
resultPromise = operation(undefined, undefined, undefined);
|
|
171
237
|
break;
|
|
172
238
|
default: // no concurrency but with history
|
|
@@ -180,10 +246,12 @@ class PromiseUtils {
|
|
|
180
246
|
exports.PromiseUtils = PromiseUtils;
|
|
181
247
|
PromiseUtils.synchronizationLocks = new Map();
|
|
182
248
|
exports.repeat = PromiseUtils.repeat;
|
|
249
|
+
exports.withRetry = PromiseUtils.withRetry;
|
|
183
250
|
exports.inParallel = PromiseUtils.inParallel;
|
|
184
251
|
exports.delayedResolve = PromiseUtils.delayedResolve;
|
|
185
252
|
exports.delayedReject = PromiseUtils.delayedReject;
|
|
186
253
|
exports.timeoutResolve = PromiseUtils.timeoutResolve;
|
|
187
254
|
exports.timeoutReject = PromiseUtils.timeoutReject;
|
|
188
255
|
exports.synchronized = PromiseUtils.synchronized;
|
|
256
|
+
exports.synchronised = PromiseUtils.synchronized;
|
|
189
257
|
exports.promiseState = PromiseUtils.promiseState;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@handy-common-utils/promise-utils",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Promise related utilities",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"pretest": "eslint . --ext .ts",
|
|
7
|
-
"test": "nyc mocha
|
|
7
|
+
"test": "nyc mocha",
|
|
8
8
|
"prepare": "shx rm -rf dist && tsc && es-check",
|
|
9
9
|
"preversion": "generate-api-docs-and-update-readme && git add README.md"
|
|
10
10
|
},
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"main": "dist/promise-utils.js",
|
|
16
16
|
"types": "dist/promise-utils.d.ts",
|
|
17
17
|
"bin": {},
|
|
18
|
-
"dependencies": {},
|
|
19
18
|
"devDependencies": {
|
|
20
|
-
"@handy-common-utils/dev-dependencies": "^1.0.
|
|
21
|
-
"
|
|
19
|
+
"@handy-common-utils/dev-dependencies": "^1.0.21",
|
|
20
|
+
"@types/chai-as-promised": "^7.1.3",
|
|
21
|
+
"chai-as-promised": "^7.1.1",
|
|
22
|
+
"es-check": "^5.2.3"
|
|
22
23
|
},
|
|
23
24
|
"publishConfig": {
|
|
24
25
|
"access": "public"
|