1 | /**
|
2 | * Utility helpers to work with promises.
|
3 | *
|
4 | * @module promise
|
5 | */
|
6 |
|
7 | import * as time from './time.js'
|
8 |
|
9 | /**
|
10 | * @template T
|
11 | * @callback PromiseResolve
|
12 | * @param {T|PromiseLike<T>} [result]
|
13 | */
|
14 |
|
15 | /**
|
16 | * @template T
|
17 | * @param {function(PromiseResolve<T>,function(Error):void):any} f
|
18 | * @return {Promise<T>}
|
19 | */
|
20 | export const create = f => /** @type {Promise<T>} */ (new Promise(f))
|
21 |
|
22 | /**
|
23 | * @param {function(function():void,function(Error):void):void} f
|
24 | * @return {Promise<void>}
|
25 | */
|
26 | export const createEmpty = f => new Promise(f)
|
27 |
|
28 | /**
|
29 | * `Promise.all` wait for all promises in the array to resolve and return the result
|
30 | * @template {unknown[] | []} PS
|
31 | *
|
32 | * @param {PS} ps
|
33 | * @return {Promise<{ -readonly [P in keyof PS]: Awaited<PS[P]> }>}
|
34 | */
|
35 | export const all = Promise.all.bind(Promise)
|
36 |
|
37 | /**
|
38 | * @param {Error} [reason]
|
39 | * @return {Promise<never>}
|
40 | */
|
41 | export const reject = reason => Promise.reject(reason)
|
42 |
|
43 | /**
|
44 | * @template T
|
45 | * @param {T|void} res
|
46 | * @return {Promise<T|void>}
|
47 | */
|
48 | export const resolve = res => Promise.resolve(res)
|
49 |
|
50 | /**
|
51 | * @template T
|
52 | * @param {T} res
|
53 | * @return {Promise<T>}
|
54 | */
|
55 | export const resolveWith = res => Promise.resolve(res)
|
56 |
|
57 | /**
|
58 | * @todo Next version, reorder parameters: check, [timeout, [intervalResolution]]
|
59 | * @deprecated use untilAsync instead
|
60 | *
|
61 | * @param {number} timeout
|
62 | * @param {function():boolean} check
|
63 | * @param {number} [intervalResolution]
|
64 | * @return {Promise<void>}
|
65 | */
|
66 | export const until = (timeout, check, intervalResolution = 10) => create((resolve, reject) => {
|
67 | const startTime = time.getUnixTime()
|
68 | const hasTimeout = timeout > 0
|
69 | const untilInterval = () => {
|
70 | if (check()) {
|
71 | clearInterval(intervalHandle)
|
72 | resolve()
|
73 | } else if (hasTimeout) {
|
74 | /* c8 ignore else */
|
75 | if (time.getUnixTime() - startTime > timeout) {
|
76 | clearInterval(intervalHandle)
|
77 | reject(new Error('Timeout'))
|
78 | }
|
79 | }
|
80 | }
|
81 | const intervalHandle = setInterval(untilInterval, intervalResolution)
|
82 | })
|
83 |
|
84 | /**
|
85 | * @param {()=>Promise<boolean>|boolean} check
|
86 | * @param {number} timeout
|
87 | * @param {number} intervalResolution
|
88 | * @return {Promise<void>}
|
89 | */
|
90 | export const untilAsync = async (check, timeout = 0, intervalResolution = 10) => {
|
91 | const startTime = time.getUnixTime()
|
92 | const noTimeout = timeout <= 0
|
93 | // eslint-disable-next-line no-unmodified-loop-condition
|
94 | while (noTimeout || time.getUnixTime() - startTime <= timeout) {
|
95 | if (await check()) return
|
96 | await wait(intervalResolution)
|
97 | }
|
98 | throw new Error('Timeout')
|
99 | }
|
100 |
|
101 | /**
|
102 | * @param {number} timeout
|
103 | * @return {Promise<undefined>}
|
104 | */
|
105 | export const wait = timeout => create((resolve, _reject) => setTimeout(resolve, timeout))
|
106 |
|
107 | /**
|
108 | * Checks if an object is a promise using ducktyping.
|
109 | *
|
110 | * Promises are often polyfilled, so it makes sense to add some additional guarantees if the user of this
|
111 | * library has some insane environment where global Promise objects are overwritten.
|
112 | *
|
113 | * @param {any} p
|
114 | * @return {boolean}
|
115 | */
|
116 | export const isPromise = p => p instanceof Promise || (p && p.then && p.catch && p.finally)
|