forked from exadel-inc/esl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
promise.ts
131 lines (120 loc) · 4.2 KB
/
promise.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
import type {AnyToAnyFnSignature} from '../misc/functions';
/** Interface to describe abstract listenable target */
export type ListenableTarget = {
addEventListener: (
event: string,
callback: (payload: any) => void,
options?: boolean | AddEventListenerOptions | undefined
) => void;
removeEventListener: (
event: string,
callback: (payload: any) => void,
options?: boolean | AddEventListenerOptions | undefined
) => void;
};
/** Deferred object represents promise with it's resolve/reject methods */
export type Deferred<T> = {
/** Wrapped promise */
promise: Promise<T>;
/** Function that resolves wrapped promise */
resolve: (arg: T) => void;
/** Function that rejects wrapped promise */
reject: (arg?: any) => void;
};
/** Return function type with the same signature but with the result type wrapped into promise */
export type PromisifyResultFn<F extends AnyToAnyFnSignature> =
((...args: Parameters<F>) => Promise<ReturnType<F> | void>);
/**
* Promise utils helper class
*/
export abstract class PromiseUtils {
/**
* @return {Promise} that will be resolved in {@param timeout} with optional {@param payload}
*/
static fromTimeout<T>(timeout: number, payload?: T): Promise<T> {
return new Promise<T>((resolve) =>
setTimeout(resolve.bind(null, payload), timeout)
);
}
/**
* @return {Promise} that will be resolved by dispatching {@param event} on {@param target}
* Or it will be rejected in {@param timeout} if it's specified
* Optional {@param options} for addEventListener can be also specified
*/
static fromEvent(
target: ListenableTarget,
event: string,
timeout?: number | null | undefined,
options?: boolean | AddEventListenerOptions
): Promise<Event> {
return new Promise((resolve, reject) => {
function eventCallback(e: Event) {
target.removeEventListener(event, eventCallback, options);
resolve(e);
}
target.addEventListener(event, eventCallback, options);
if (typeof timeout === 'number' && timeout >= 0) {
setTimeout(() => reject(new Error('Rejected by timeout')), timeout);
}
});
}
/**
* Short helper to make Promise from element state marker
* Marker should be accessible and listenable
* @example
* const imgReady = PromiseUtils.fromMarker(eslImage, 'ready');
*/
static fromMarker(target: HTMLElement, marker: string, event?: string): Promise<HTMLElement> {
if ((target as any)[marker]) return Promise.resolve(target);
return PromiseUtils.fromEvent(target, event || marker).then(() => target);
}
/**
* Safe wrap for Promise.resolve to use in Promise chain
* @example
* const resolvedPromise = rejectedPromise.catch(PromiseUtils.resolve);
*/
static resolve<T>(arg: T | PromiseLike<T>): Promise<T> {
return Promise.resolve(arg);
}
/**
* Safe wrap for Promise.reject to use in Promise chain
* @example
* const rejectedPromise = resolvedPromise.then(PromiseUtils.resolve);
*/
static reject<T = never>(arg?: T | PromiseLike<T>): Promise<T> {
return Promise.reject(arg);
}
/**
* Call {@param callback} limited by {@param tryCount} amount of times with interval in {@param timeout} ms
* @return {Promise} that will be resolved as soon as callback returns truthy value, or reject it by limit.
*/
static tryUntil<T>(callback: () => T, tryCount = 2, timeout = 100): Promise<T> {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
let result: T | undefined;
try {
result = callback();
} catch {
result = undefined;
}
if (result || --tryCount < 0) {
clearInterval(interval);
result ? resolve(result) : reject(new Error('Rejected by limit of tries'));
}
}, timeout);
});
}
/**
* Create Deferred Object that wraps promise and its resolve and reject callbacks
*/
static deferred<T>(): Deferred<T> {
let reject: any;
let resolve: any;
// Both reject and resolve will be assigned anyway while the Promise constructing.
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return {promise, resolve, reject};
}
}