1
|
|
|
var Race = require('./Race').Race |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @callback Future~resolver |
5
|
|
|
* |
6
|
|
|
* @param {Function} resolve |
7
|
|
|
* @param {Function} reject |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* @enum |
12
|
|
|
* @readonly |
13
|
|
|
*/ |
14
|
|
|
var Status = { |
15
|
|
|
Pending: 0, |
16
|
|
|
Resolving: 1, |
17
|
|
|
Rejected: 2, |
18
|
|
|
Fulfilled: 3 |
19
|
|
|
} |
20
|
|
|
|
21
|
|
|
Status.name = function (value) { |
22
|
|
|
return Object.keys(Status).reduce(function (carrier, key) { |
23
|
|
|
return carrier || (Status[key] === value ? key : null) |
24
|
|
|
}, null) |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* This is a very standard promise interface implementation, but with ability |
29
|
|
|
* to be externally completed or cancelled by `#resolve()` and `#reject()` |
30
|
|
|
* methods. |
31
|
|
|
* |
32
|
|
|
* It is named after Java's CompletableFuture. |
33
|
|
|
* |
34
|
|
|
* **NB:** this implementation doesn't account for cyclic references (which are |
35
|
|
|
* tremendously easy to implement). |
36
|
|
|
* |
37
|
|
|
* @class |
38
|
|
|
* @template T |
39
|
|
|
* |
40
|
|
|
* @param {Future~resolver} [resolver] Standard promise resolver |
41
|
|
|
*/ |
42
|
|
|
function Future (resolver) { |
43
|
|
|
var self = this |
44
|
|
|
var status = Status.Pending |
45
|
|
|
var identity |
46
|
|
|
var propagation = null |
47
|
|
|
var queue = [] |
48
|
|
|
|
49
|
|
|
this.getValue = function () { return identity } |
50
|
|
|
|
51
|
|
|
this.getStatus = function () { return status } |
52
|
|
|
|
53
|
|
|
this.hasStatus = function (s) { return status === s } |
54
|
|
|
|
55
|
|
|
this.isPending = function () { return self.hasStatus(Status.Pending) } |
56
|
|
|
this.isFulfilled = function () { return self.hasStatus(Status.Fulfilled) } |
57
|
|
|
this.isRejected = function () { return self.hasStatus(Status.Rejected) } |
58
|
|
|
this.isResolved = function () { |
59
|
|
|
return status === Status.Fulfilled || status === Status.Rejected |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
function schedulePropagation () { |
63
|
|
|
// if already scheduled |
64
|
|
|
if (propagation) { return } |
65
|
|
|
// @see https://promisesaplus.com/#point-34 |
66
|
|
|
// setTimeout is used because process.nextTick is not something one mocks |
67
|
|
|
propagation = setTimeout(function () { |
68
|
|
|
propagation = null |
69
|
|
|
propagate() |
70
|
|
|
}, 0) |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
function propagate () { |
74
|
|
|
if (!self.isResolved()) { return } |
75
|
|
|
var staged = queue |
76
|
|
|
queue = [] |
77
|
|
|
staged.forEach(function (callback) { |
78
|
|
|
callback(status, identity) |
79
|
|
|
}) |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
function setIdentity (nextStatus, value) { |
83
|
|
|
if (self.isResolved()) { return } |
84
|
|
|
status = nextStatus |
85
|
|
|
identity = value |
86
|
|
|
schedulePropagation() |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
var fulfill = setIdentity.bind(self, Status.Fulfilled) |
90
|
|
|
var reject = setIdentity.bind(self, Status.Rejected) |
91
|
|
|
|
92
|
|
|
function resolve (nextStatus, value) { |
93
|
|
|
if (!self.isPending()) { return self } |
94
|
|
|
extendedResolutionProcedure(nextStatus, value) |
95
|
|
|
return self |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Resolves (fulfills) current instance with provided value |
100
|
|
|
* |
101
|
|
|
* @param {T} [value] |
102
|
|
|
* @returns {Future.<T>} Current instance |
103
|
|
|
*/ |
104
|
|
|
this.fulfill = resolve.bind(this, Status.Fulfilled) |
105
|
|
|
|
106
|
|
|
this.resolve = this.fulfill |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Rejects current instance with provided value |
110
|
|
|
* |
111
|
|
|
* @param {T} [value] |
112
|
|
|
* @returns {Future.<T>} Current instance |
113
|
|
|
*/ |
114
|
|
|
this.reject = resolve.bind(this, Status.Rejected) |
115
|
|
|
|
116
|
|
|
if (resolver) { resolver(this.resolve, this.reject) } |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @param x |
120
|
|
|
*/ |
121
|
|
|
function promiseResolutionProcedure (x) { |
122
|
|
|
if (self.isResolved()) { return } |
123
|
|
|
status = Status.Resolving |
124
|
|
|
if (x === self) { |
125
|
|
|
var message = 'Can\'t resolve promise with itself' |
126
|
|
|
return setIdentity(Status.Rejected, new TypeError(message)) |
127
|
|
|
} |
128
|
|
|
if (x instanceof Future && x.isResolved()) { |
129
|
|
|
return extendedResolutionProcedure(x.getStatus(), x.getValue()) |
130
|
|
|
} |
131
|
|
|
if (!x || (typeof x !== 'function' && typeof x !== 'object')) { |
132
|
|
|
return fulfill(x) |
133
|
|
|
} |
134
|
|
|
var race = new Race(1) |
135
|
|
|
var resolvePromise = race.racer(promiseResolutionProcedure) |
136
|
|
|
var rejectPromise = race.racer(reject) |
137
|
|
|
try { |
138
|
|
|
var then = x.then |
139
|
|
|
if (typeof then !== 'function') { return fulfill(x) } |
140
|
|
|
then.call(x, resolvePromise, rejectPromise) |
141
|
|
|
} catch (e) { |
142
|
|
|
rejectPromise(e) |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
function extendedResolutionProcedure (status, x) { |
147
|
|
|
status === Status.Rejected ? reject(x) : promiseResolutionProcedure(x) |
148
|
|
|
return self |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Standard then-implementation |
153
|
|
|
* |
154
|
|
|
* @param {Function} [onFulfill] |
155
|
|
|
* @param {Function} [onReject] |
156
|
|
|
* @returns {Future.<T>} |
157
|
|
|
*/ |
158
|
|
|
this.then = function (onFulfill, onReject) { |
159
|
|
|
if (typeof onFulfill !== 'function') { onFulfill = null } |
160
|
|
|
if (typeof onReject !== 'function') { onReject = null } |
161
|
|
|
if (!onFulfill && !onReject) { return self } |
162
|
|
|
onReject = onReject || function (error) { throw error } |
163
|
|
|
onFulfill = onFulfill || function (value) { return value } |
164
|
|
|
var target = new Future() |
165
|
|
|
var callback = function (status, value) { |
166
|
|
|
var handler = status === Status.Fulfilled ? onFulfill : onReject |
167
|
|
|
try { |
168
|
|
|
target.resolve(handler(value)) |
169
|
|
|
} catch (e) { |
170
|
|
|
target.reject(e) |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
queue.push(callback) |
174
|
|
|
schedulePropagation() |
175
|
|
|
return target |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Attaches current promise to provided thenable, accepting it's eventual |
180
|
|
|
* outcome. |
181
|
|
|
* |
182
|
|
|
* @param {Thenable} promise |
183
|
|
|
* |
184
|
|
|
* @return {Future} |
185
|
|
|
*/ |
186
|
|
|
this.wrap = function (promise) { |
187
|
|
|
promise.then(function (value) { |
188
|
|
|
self.fulfill(value) |
189
|
|
|
}, function (e) { |
190
|
|
|
self.reject(e) |
191
|
|
|
}) |
192
|
|
|
return self |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
this.toString = function () { |
196
|
|
|
var state = Status.name(status) |
197
|
|
|
return 'Future <' + state + (identity ? ':' + identity : '') + '>' |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Returns promise that awaits all passed promises. |
203
|
|
|
* |
204
|
|
|
* @param {Array.<Promise.<*>|Thenable<*>>} promises |
205
|
|
|
* @returns {Future.<*>} |
206
|
|
|
*/ |
207
|
|
|
Future.all = function (promises) { |
208
|
|
|
return Future.wrap(Promise.all(promises)) |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Returns result of first resolved promise, be it fulfillment or rejection |
213
|
|
|
* |
214
|
|
|
* @param {Array.<Promise.<*>|Thenable<*>>} promises |
215
|
|
|
* @returns {Future.<*>} |
216
|
|
|
*/ |
217
|
|
|
Future.race = function (promises) { |
218
|
|
|
return Future.wrap(Promise.race(promises)) |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Returns resolved promise. |
223
|
|
|
* |
224
|
|
|
* @param {*} [value] |
225
|
|
|
* @returns {Future.<*>} |
226
|
|
|
*/ |
227
|
|
|
Future.resolve = function (value) { |
228
|
|
|
return new Future().resolve(value) |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Returns rejected promise. |
233
|
|
|
* |
234
|
|
|
* @param {*} [value] |
235
|
|
|
* @returns {Future.<*>} |
236
|
|
|
*/ |
237
|
|
|
Future.reject = function (value) { |
238
|
|
|
return new Future().reject(value) |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Wraps given promise in a Future, giving user code an option |
243
|
|
|
* to reject/resolve it. |
244
|
|
|
* |
245
|
|
|
* @param {Promise.<*>|Thenable.<*>} promise |
246
|
|
|
* @returns {Future.<*>} |
247
|
|
|
*/ |
248
|
|
|
Future.wrap = function (promise) { |
249
|
|
|
return new Future().wrap(promise) |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
Future.Status = Status |
253
|
|
|
|
254
|
|
|
module.exports = { |
255
|
|
|
Future: Future |
256
|
|
|
} |
257
|
|
|
|