OLD | NEW |
---|---|
1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 (function(global, utils, extrasUtils) { | 5 (function(global, utils, extrasUtils) { |
6 | 6 |
7 "use strict"; | 7 "use strict"; |
8 | 8 |
9 %CheckIsBootstrapping(); | 9 %CheckIsBootstrapping(); |
10 | 10 |
(...skipping 13 matching lines...) Expand all Loading... | |
24 var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); | 24 var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); |
25 var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); | 25 var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); |
26 var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); | 26 var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); |
27 | 27 |
28 utils.Import(function(from) { | 28 utils.Import(function(from) { |
29 MakeTypeError = from.MakeTypeError; | 29 MakeTypeError = from.MakeTypeError; |
30 }); | 30 }); |
31 | 31 |
32 // ------------------------------------------------------------------- | 32 // ------------------------------------------------------------------- |
33 | 33 |
34 // Status values: 0 = pending, +1 = resolved, -1 = rejected | 34 // Status values: 0 = pending, +1 = resolved, -1 = rejected |
Camillo Bruni
2015/12/28 12:22:35
Has adding named constants for the status values a
| |
35 var lastMicrotaskId = 0; | 35 var lastMicrotaskId = 0; |
36 | 36 |
37 function CreateResolvingFunctions(promise) { | 37 function CreateResolvingFunctions(promise) { |
38 var alreadyResolved = false; | 38 var alreadyResolved = false; |
39 | 39 |
40 var resolve = function(value) { | 40 var resolve = function(value) { |
41 if (alreadyResolved === true) return; | 41 if (alreadyResolved === true) return; |
42 alreadyResolved = true; | 42 alreadyResolved = true; |
43 if (value === promise) { | |
44 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value)); | |
45 } | |
46 PromiseResolve(promise, value); | 43 PromiseResolve(promise, value); |
47 }; | 44 }; |
48 | 45 |
49 var reject = function(reason) { | 46 var reject = function(reason) { |
50 if (alreadyResolved === true) return; | 47 if (alreadyResolved === true) return; |
51 alreadyResolved = true; | 48 alreadyResolved = true; |
52 PromiseReject(promise, reason); | 49 PromiseReject(promise, reason); |
53 }; | 50 }; |
54 | 51 |
55 return { | 52 return { |
56 __proto__: null, | 53 __proto__: null, |
57 resolve: resolve, | 54 resolve: resolve, |
58 reject: reject | 55 reject: reject |
59 }; | 56 }; |
60 } | 57 } |
61 | 58 |
62 | 59 |
63 var GlobalPromise = function Promise(resolver) { | 60 var GlobalPromise = function Promise(resolver) { |
64 if (resolver === promiseRawSymbol) { | 61 if (resolver === promiseRawSymbol) { |
65 return %NewObject(GlobalPromise, new.target); | 62 return %NewObject(GlobalPromise, new.target); |
66 } | 63 } |
67 if (IS_UNDEFINED(new.target)) throw MakeTypeError(kNotAPromise, this); | 64 if (IS_UNDEFINED(new.target)) throw MakeTypeError(kNotAPromise, this); |
68 if (!IS_CALLABLE(resolver)) | 65 if (!IS_CALLABLE(resolver)) |
69 throw MakeTypeError(kResolverNotAFunction, resolver); | 66 throw MakeTypeError(kResolverNotAFunction, resolver); |
Camillo Bruni
2015/12/28 12:22:35
please add braces here.
| |
70 | 67 |
71 var promise = PromiseInit(%NewObject(GlobalPromise, new.target)); | 68 var promise = PromiseInit(%NewObject(GlobalPromise, new.target)); |
72 | 69 |
73 try { | 70 try { |
74 %DebugPushPromise(promise, Promise); | 71 %DebugPushPromise(promise, Promise); |
75 var callbacks = CreateResolvingFunctions(promise); | 72 var callbacks = CreateResolvingFunctions(promise); |
76 resolver(callbacks.resolve, callbacks.reject); | 73 resolver(callbacks.resolve, callbacks.reject); |
77 } catch (e) { | 74 } catch (e) { |
78 PromiseReject(promise, e); | 75 PromiseReject(promise, e); |
79 } finally { | 76 } finally { |
(...skipping 24 matching lines...) Expand all Loading... | |
104 } | 101 } |
105 | 102 |
106 function PromiseInit(promise) { | 103 function PromiseInit(promise) { |
107 return PromiseSet( | 104 return PromiseSet( |
108 promise, 0, UNDEFINED, new InternalArray, new InternalArray) | 105 promise, 0, UNDEFINED, new InternalArray, new InternalArray) |
109 } | 106 } |
110 | 107 |
111 function PromiseDone(promise, status, value, promiseQueue) { | 108 function PromiseDone(promise, status, value, promiseQueue) { |
112 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { | 109 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { |
113 var tasks = GET_PRIVATE(promise, promiseQueue); | 110 var tasks = GET_PRIVATE(promise, promiseQueue); |
114 if (tasks.length) PromiseEnqueue(value, tasks, status); | 111 if (tasks.length) PromiseEnqueue(value, tasks, status); |
Camillo Bruni
2015/12/28 12:22:35
According to the spec that's step 8, probably irre
| |
115 PromiseSet(promise, status, value); | 112 PromiseSet(promise, status, value); |
Camillo Bruni
2015/12/28 12:22:35
I'd make it explicit that you set the [[PromiseFul
| |
116 } | 113 } |
117 } | 114 } |
118 | 115 |
119 function PromiseCoerce(constructor, x) { | |
120 if (!IsPromise(x) && IS_RECEIVER(x)) { | |
121 var then; | |
122 try { | |
123 then = x.then; | |
124 } catch(r) { | |
125 return %_Call(PromiseRejected, constructor, r); | |
126 } | |
127 if (IS_CALLABLE(then)) { | |
128 var deferred = NewPromiseCapability(constructor); | |
129 try { | |
130 %_Call(then, x, deferred.resolve, deferred.reject); | |
131 } catch(r) { | |
132 deferred.reject(r); | |
133 } | |
134 return deferred.promise; | |
135 } | |
136 } | |
137 return x; | |
138 } | |
139 | |
140 function PromiseHandle(value, handler, deferred) { | 116 function PromiseHandle(value, handler, deferred) { |
141 try { | 117 try { |
142 %DebugPushPromise(deferred.promise, PromiseHandle); | 118 %DebugPushPromise(deferred.promise, PromiseHandle); |
143 var result = handler(value); | 119 var result = handler(value); |
144 if (result === deferred.promise) | 120 deferred.resolve(result); |
145 throw MakeTypeError(kPromiseCyclic, result); | |
146 else if (IsPromise(result)) | |
147 %_Call(PromiseChain, result, deferred.resolve, deferred.reject); | |
148 else | |
149 deferred.resolve(result); | |
150 } catch (exception) { | 121 } catch (exception) { |
151 try { deferred.reject(exception); } catch (e) { } | 122 try { deferred.reject(exception); } catch (e) { } |
152 } finally { | 123 } finally { |
153 %DebugPopPromise(); | 124 %DebugPopPromise(); |
154 } | 125 } |
155 } | 126 } |
156 | 127 |
157 function PromiseEnqueue(value, tasks, status) { | 128 function PromiseEnqueue(value, tasks, status) { |
158 var id, name, instrumenting = DEBUG_IS_ACTIVE; | 129 var id, name, instrumenting = DEBUG_IS_ACTIVE; |
159 %EnqueueMicrotask(function() { | 130 %EnqueueMicrotask(function() { |
(...skipping 26 matching lines...) Expand all Loading... | |
186 | 157 |
187 function IsPromise(x) { | 158 function IsPromise(x) { |
188 return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); | 159 return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); |
189 } | 160 } |
190 | 161 |
191 function PromiseCreate() { | 162 function PromiseCreate() { |
192 return new GlobalPromise(PromiseNopResolver) | 163 return new GlobalPromise(PromiseNopResolver) |
193 } | 164 } |
194 | 165 |
195 function PromiseResolve(promise, x) { | 166 function PromiseResolve(promise, x) { |
196 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { | 167 if (x === promise) { |
197 if (IS_RECEIVER(x)) { | 168 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x)); |
198 // 25.4.1.3.2 steps 8-12 | 169 } |
199 try { | 170 if (IS_RECEIVER(x)) { |
Camillo Bruni
2015/12/28 12:22:35
Early return would make this more readable, and ea
| |
200 var then = x.then; | 171 // 25.4.1.3.2 steps 8-12 |
201 } catch (e) { | 172 try { |
202 return PromiseReject(promise, e); | 173 var then = x.then; |
174 } catch (e) { | |
175 return PromiseReject(promise, e); | |
176 } | |
177 if (IS_CALLABLE(then)) { | |
Camillo Bruni
2015/12/28 12:22:35
I'd say the same here ;) (yes, yes nit-picking :P)
| |
178 // PromiseResolveThenableJob | |
179 var id, name, instrumenting = DEBUG_IS_ACTIVE; | |
180 %EnqueueMicrotask(function() { | |
181 if (instrumenting) { | |
182 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); | |
183 } | |
184 try { | |
185 var callbacks = CreateResolvingFunctions(promise); | |
186 %_Call(then, x, callbacks.resolve, callbacks.reject); | |
187 } catch (e) { | |
188 PromiseReject(promise, e); | |
189 } | |
190 if (instrumenting) { | |
191 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); | |
192 } | |
193 }); | |
194 if (instrumenting) { | |
195 id = ++lastMicrotaskId; | |
196 name = "PromseResolveThenableJob"; | |
197 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); | |
203 } | 198 } |
204 if (IS_CALLABLE(then)) { | 199 return; |
205 // PromiseResolveThenableJob | |
206 return %EnqueueMicrotask(function() { | |
207 try { | |
208 var callbacks = CreateResolvingFunctions(promise); | |
209 %_Call(then, x, callbacks.resolve, callbacks.reject); | |
210 } catch (e) { | |
211 PromiseReject(promise, e); | |
212 } | |
213 }); | |
214 } | |
215 } | 200 } |
216 PromiseDone(promise, +1, x, promiseOnResolveSymbol); | |
217 } | 201 } |
202 PromiseDone(promise, +1, x, promiseOnResolveSymbol); | |
218 } | 203 } |
219 | 204 |
220 function PromiseReject(promise, r) { | 205 function PromiseReject(promise, r) { |
221 // Check promise status to confirm that this reject has an effect. | 206 // Check promise status to confirm that this reject has an effect. |
222 // Call runtime for callbacks to the debugger or for unhandled reject. | 207 // Call runtime for callbacks to the debugger or for unhandled reject. |
223 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { | 208 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { |
224 var debug_is_active = DEBUG_IS_ACTIVE; | 209 var debug_is_active = DEBUG_IS_ACTIVE; |
225 if (debug_is_active || | 210 if (debug_is_active || |
226 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { | 211 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { |
227 %PromiseRejectEvent(promise, r, debug_is_active); | 212 %PromiseRejectEvent(promise, r, debug_is_active); |
(...skipping 10 matching lines...) Expand all Loading... | |
238 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); | 223 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); |
239 var callbacks = CreateResolvingFunctions(promise); | 224 var callbacks = CreateResolvingFunctions(promise); |
240 return { | 225 return { |
241 promise: promise, | 226 promise: promise, |
242 resolve: callbacks.resolve, | 227 resolve: callbacks.resolve, |
243 reject: callbacks.reject | 228 reject: callbacks.reject |
244 }; | 229 }; |
245 } else { | 230 } else { |
246 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; | 231 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; |
247 result.promise = new C(function(resolve, reject) { | 232 result.promise = new C(function(resolve, reject) { |
233 // TODO(littledan): Check for resolve and reject being not undefined | |
248 result.resolve = resolve; | 234 result.resolve = resolve; |
249 result.reject = reject; | 235 result.reject = reject; |
250 }); | 236 }); |
251 return result; | 237 return result; |
252 } | 238 } |
253 } | 239 } |
254 | 240 |
255 function PromiseDeferred() { | 241 function PromiseDeferred() { |
256 return NewPromiseCapability(this); | 242 return NewPromiseCapability(this); |
257 } | 243 } |
258 | 244 |
259 function PromiseResolved(x) { | 245 function PromiseResolved(x) { |
260 if (this === GlobalPromise) { | 246 return %_Call(PromiseCast, this, x); |
261 // Optimized case, avoid extra closure. | |
262 return PromiseCreateAndSet(+1, x); | |
263 } else { | |
264 return new this(function(resolve, reject) { resolve(x) }); | |
265 } | |
266 } | 247 } |
267 | 248 |
268 function PromiseRejected(r) { | 249 function PromiseRejected(r) { |
Camillo Bruni
2015/12/28 12:22:35
Dangerous name if you ask me :) PromiseReject vs.
| |
250 if (!IS_RECEIVER(this)) { | |
251 throw MakeTypeError(kCalledOnNonObject, PromiseRejected); | |
252 } | |
269 var promise; | 253 var promise; |
270 if (this === GlobalPromise) { | 254 if (this === GlobalPromise) { |
271 // Optimized case, avoid extra closure. | 255 // Optimized case, avoid extra closure. |
272 promise = PromiseCreateAndSet(-1, r); | 256 promise = PromiseCreateAndSet(-1, r); |
273 // The debug event for this would always be an uncaught promise reject, | 257 // The debug event for this would always be an uncaught promise reject, |
274 // which is usually simply noise. Do not trigger that debug event. | 258 // which is usually simply noise. Do not trigger that debug event. |
275 %PromiseRejectEvent(promise, r, false); | 259 %PromiseRejectEvent(promise, r, false); |
276 } else { | 260 } else { |
277 promise = new this(function(resolve, reject) { reject(r) }); | 261 promise = new this(function(resolve, reject) { reject(r) }); |
278 } | 262 } |
279 return promise; | 263 return promise; |
280 } | 264 } |
281 | 265 |
282 // Simple chaining. | 266 // Multi-unwrapped chaining with thenable coercion. |
283 | 267 |
284 // PromiseChain a.k.a. flatMap | 268 function PromiseThen(onResolve, onReject) { |
285 function PromiseChainInternal(constructor, onResolve, onReject) { | 269 var constructor = this.constructor; |
286 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; | 270 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; |
287 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; | 271 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; |
288 var deferred = NewPromiseCapability(constructor); | 272 var deferred = NewPromiseCapability(constructor); |
289 switch (GET_PRIVATE(this, promiseStatusSymbol)) { | 273 switch (GET_PRIVATE(this, promiseStatusSymbol)) { |
290 case UNDEFINED: | 274 case UNDEFINED: |
275 // TODO(littledan): The type check should be called before | |
276 // constructing NewPromiseCapability; this is observable when | |
277 // erroneously copying this method to other classes. | |
291 throw MakeTypeError(kNotAPromise, this); | 278 throw MakeTypeError(kNotAPromise, this); |
292 case 0: // Pending | 279 case 0: // Pending |
293 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); | 280 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); |
294 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); | 281 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); |
295 break; | 282 break; |
296 case +1: // Resolved | 283 case +1: // Resolved |
297 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), | 284 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), |
298 [onResolve, deferred], | 285 [onResolve, deferred], |
299 +1); | 286 +1); |
300 break; | 287 break; |
301 case -1: // Rejected | 288 case -1: // Rejected |
302 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { | 289 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { |
303 // Promise has already been rejected, but had no handler. | 290 // Promise has already been rejected, but had no handler. |
304 // Revoke previously triggered reject event. | 291 // Revoke previously triggered reject event. |
305 %PromiseRevokeReject(this); | 292 %PromiseRevokeReject(this); |
306 } | 293 } |
307 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), | 294 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), |
308 [onReject, deferred], | 295 [onReject, deferred], |
309 -1); | 296 -1); |
310 break; | 297 break; |
311 } | 298 } |
312 // Mark this promise as having handler. | 299 // Mark this promise as having handler. |
313 SET_PRIVATE(this, promiseHasHandlerSymbol, true); | 300 SET_PRIVATE(this, promiseHasHandlerSymbol, true); |
Camillo Bruni
2015/12/28 12:22:35
Shouldn't this happen only in the rejected case? (
| |
314 if (DEBUG_IS_ACTIVE) { | 301 if (DEBUG_IS_ACTIVE) { |
315 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); | 302 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); |
316 } | 303 } |
317 return deferred.promise; | 304 return deferred.promise; |
318 } | 305 } |
319 | 306 |
307 // Chain is left around for now as an alias for then | |
320 function PromiseChain(onResolve, onReject) { | 308 function PromiseChain(onResolve, onReject) { |
321 return %_Call(PromiseChainInternal, this, this.constructor, | 309 return %_Call(PromiseThen, this, onResolve, onReject); |
322 onResolve, onReject); | |
323 } | 310 } |
324 | 311 |
325 function PromiseCatch(onReject) { | 312 function PromiseCatch(onReject) { |
326 return this.then(UNDEFINED, onReject); | 313 return this.then(UNDEFINED, onReject); |
327 } | 314 } |
328 | 315 |
329 // Multi-unwrapped chaining with thenable coercion. | |
330 | |
331 function PromiseThen(onResolve, onReject) { | |
332 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; | |
333 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; | |
334 var that = this; | |
335 var constructor = this.constructor; | |
336 return %_Call( | |
337 PromiseChainInternal, | |
338 this, | |
339 constructor, | |
340 function(x) { | |
341 x = PromiseCoerce(constructor, x); | |
342 if (x === that) { | |
343 return onReject(MakeTypeError(kPromiseCyclic, x)); | |
344 } else if (IsPromise(x)) { | |
345 return x.then(onResolve, onReject); | |
346 } else { | |
347 return onResolve(x); | |
348 } | |
349 }, | |
350 onReject | |
351 ); | |
352 } | |
353 | |
354 // Combinators. | 316 // Combinators. |
355 | 317 |
356 function PromiseCast(x) { | 318 function PromiseCast(x) { |
Camillo Bruni
2015/12/28 12:22:35
I know this is probably not very important, but as
| |
319 if (!IS_RECEIVER(this)) { | |
320 throw MakeTypeError(kCalledOnNonObject, PromiseCast); | |
321 } | |
357 if (IsPromise(x) && x.constructor === this) { | 322 if (IsPromise(x) && x.constructor === this) { |
358 return x; | 323 return x; |
359 } else { | 324 } else { |
360 return new this(function(resolve) { resolve(x) }); | 325 return new this(function(resolve, reject) { resolve(x) }); |
361 } | 326 } |
362 } | 327 } |
363 | 328 |
364 function PromiseAll(iterable) { | 329 function PromiseAll(iterable) { |
365 var deferred = NewPromiseCapability(this); | 330 var deferred = NewPromiseCapability(this); |
366 var resolutions = []; | 331 var resolutions = []; |
367 try { | 332 try { |
368 var count = 0; | 333 var count = 0; |
369 var i = 0; | 334 var i = 0; |
370 for (var value of iterable) { | 335 for (var value of iterable) { |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
478 [PromiseChain, PromiseDeferred, PromiseResolved].forEach( | 443 [PromiseChain, PromiseDeferred, PromiseResolved].forEach( |
479 fn => %FunctionRemovePrototype(fn)); | 444 fn => %FunctionRemovePrototype(fn)); |
480 | 445 |
481 utils.Export(function(to) { | 446 utils.Export(function(to) { |
482 to.PromiseChain = PromiseChain; | 447 to.PromiseChain = PromiseChain; |
483 to.PromiseDeferred = PromiseDeferred; | 448 to.PromiseDeferred = PromiseDeferred; |
484 to.PromiseResolved = PromiseResolved; | 449 to.PromiseResolved = PromiseResolved; |
485 }); | 450 }); |
486 | 451 |
487 }) | 452 }) |
OLD | NEW |