-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
promise_helpers.ts
158 lines (146 loc) · 5.32 KB
/
promise_helpers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { isNullish } from 'web3-validator';
/**
* An alternative to the node function `isPromise` that exists in `util/types` because it is not available on the browser.
* @param object - to check if it is a `Promise`
* @returns `true` if it is an `object` or a `function` that has a `then` function. And returns `false` otherwise.
*/
export function isPromise(object: unknown): boolean {
return (
(typeof object === 'object' || typeof object === 'function') &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
typeof (object as { then: unknown }).then === 'function'
);
}
export type AsyncFunction<T, K = unknown> = (...args: K[]) => Promise<T>;
export function waitWithTimeout<T>(
awaitable: Promise<T> | AsyncFunction<T>,
timeout: number,
error: Error,
): Promise<T>;
export function waitWithTimeout<T>(
awaitable: Promise<T> | AsyncFunction<T>,
timeout: number,
): Promise<T | undefined>;
/**
* Wait for a promise but interrupt it if it did not resolve within a given timeout.
* If the timeout reached, before the promise code resolve, either throw an error if an error object was provided, or return `undefined`.
* @param awaitable - The promise or function to wait for.
* @param timeout - The timeout in milliseconds.
* @param error - (Optional) The error to throw if the timeout reached.
*/
export async function waitWithTimeout<T>(
awaitable: Promise<T> | AsyncFunction<T>,
timeout: number,
error?: Error,
): Promise<T | undefined> {
let timeoutId: NodeJS.Timeout | undefined;
const result = await Promise.race([
awaitable instanceof Promise ? awaitable : awaitable(),
new Promise<undefined | Error>((resolve, reject) => {
timeoutId = setTimeout(() => (error ? reject(error) : resolve(undefined)), timeout);
}),
]);
if (timeoutId) {
clearTimeout(timeoutId);
}
if (result instanceof Error) {
throw result;
}
return result;
}
/**
* Repeatedly calls an async function with a given interval until the result of the function is defined (not undefined or null),
* or until a timeout is reached.
* @param func - The function to call.
* @param interval - The interval in milliseconds.
*/
export async function pollTillDefined<T>(
func: AsyncFunction<T>,
interval: number,
): Promise<Exclude<T, undefined>> {
const awaitableRes = waitWithTimeout(func, interval);
let intervalId: NodeJS.Timer | undefined;
const polledRes = new Promise<Exclude<T, undefined>>((resolve, reject) => {
intervalId = setInterval(() => {
(async () => {
try {
const res = await waitWithTimeout(func, interval);
if (!isNullish(res)) {
clearInterval(intervalId);
resolve(res as unknown as Exclude<T, undefined>);
}
} catch (error) {
clearInterval(intervalId);
reject(error);
}
})() as unknown;
}, interval);
});
// If the first call to awaitableRes succeeded, return the result
const res = await awaitableRes;
if (!isNullish(res)) {
if (intervalId) {
clearInterval(intervalId);
}
return res as unknown as Exclude<T, undefined>;
}
return polledRes;
}
/**
* Enforce a timeout on a promise, so that it can be rejected if it takes too long to complete
* @param timeout - The timeout to enforced in milliseconds.
* @param error - The error to throw if the timeout is reached.
* @returns A tuple of the timeout id and the promise that will be rejected if the timeout is reached.
*
* @example
* ```ts
* const [timerId, promise] = web3.utils.rejectIfTimeout(100, new Error('time out'));
* ```
*/
export function rejectIfTimeout(timeout: number, error: Error): [NodeJS.Timer, Promise<never>] {
let timeoutId: NodeJS.Timer | undefined;
const rejectOnTimeout = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => {
reject(error);
}, timeout);
});
return [timeoutId as unknown as NodeJS.Timer, rejectOnTimeout];
}
/**
* Sets an interval that repeatedly executes the given cond function with the specified interval between each call.
* If the condition is met, the interval is cleared and a Promise that rejects with the returned value is returned.
* @param cond - The function/confition to call.
* @param interval - The interval in milliseconds.
* @returns - an array with the interval ID and the Promise.
*/
export function rejectIfConditionAtInterval<T>(
cond: AsyncFunction<T | undefined>,
interval: number,
): [NodeJS.Timer, Promise<never>] {
let intervalId: NodeJS.Timer | undefined;
const rejectIfCondition = new Promise<never>((_, reject) => {
intervalId = setInterval(() => {
(async () => {
const error = await cond();
if (error) {
clearInterval(intervalId);
reject(error);
}
})() as unknown;
}, interval);
});
return [intervalId as unknown as NodeJS.Timer, rejectIfCondition];
}