-
-
Notifications
You must be signed in to change notification settings - Fork 22
/
index.js
119 lines (98 loc) · 2.48 KB
/
index.js
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
export class CancelError extends Error {
constructor(reason) {
super(reason || 'Promise was canceled');
this.name = 'CancelError';
}
get isCanceled() {
return true;
}
}
const promiseState = Object.freeze({
pending: Symbol('pending'),
canceled: Symbol('canceled'),
resolved: Symbol('resolved'),
rejected: Symbol('rejected'),
});
export default class PCancelable {
static fn(userFunction) {
return (...arguments_) => new PCancelable((resolve, reject, onCancel) => {
arguments_.push(onCancel);
userFunction(...arguments_).then(resolve, reject);
});
}
#cancelHandlers = [];
#rejectOnCancel = true;
#state = promiseState.pending;
#promise;
#reject;
constructor(executor) {
this.#promise = new Promise((resolve, reject) => {
this.#reject = reject;
const onResolve = value => {
if (this.#state !== promiseState.canceled || !onCancel.shouldReject) {
resolve(value);
this.#setState(promiseState.resolved);
}
};
const onReject = error => {
if (this.#state !== promiseState.canceled || !onCancel.shouldReject) {
reject(error);
this.#setState(promiseState.rejected);
}
};
const onCancel = handler => {
if (this.#state !== promiseState.pending) {
throw new Error(`The \`onCancel\` handler was attached after the promise ${this.#state.description}.`);
}
this.#cancelHandlers.push(handler);
};
Object.defineProperties(onCancel, {
shouldReject: {
get: () => this.#rejectOnCancel,
set: boolean => {
this.#rejectOnCancel = boolean;
},
},
});
executor(onResolve, onReject, onCancel);
});
}
// eslint-disable-next-line unicorn/no-thenable
then(onFulfilled, onRejected) {
return this.#promise.then(onFulfilled, onRejected);
}
catch(onRejected) {
return this.#promise.catch(onRejected);
}
finally(onFinally) {
return this.#promise.finally(onFinally);
}
cancel(reason) {
if (this.#state !== promiseState.pending) {
return;
}
this.#setState(promiseState.canceled);
if (this.#cancelHandlers.length > 0) {
try {
for (const handler of this.#cancelHandlers) {
handler();
}
} catch (error) {
this.#reject(error);
return;
}
}
if (this.#rejectOnCancel) {
this.#reject(new CancelError(reason));
}
}
get isCanceled() {
return this.#state === promiseState.canceled;
}
#setState(state) {
if (this.#state === promiseState.pending) {
this.#state = state;
}
}
}
Object.setPrototypeOf(PCancelable.prototype, Promise.prototype);