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 22 matching lines...) Expand all Loading... |
33 | 33 |
34 // Status values: 0 = pending, +1 = resolved, -1 = rejected | 34 // Status values: 0 = pending, +1 = resolved, -1 = rejected |
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 { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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); |
115 PromiseSet(promise, status, value); | 112 PromiseSet(promise, status, value); |
116 } | 113 } |
117 } | 114 } |
118 | 115 |
119 function PromiseCoerce(constructor, x) { | |
120 if (!IsPromise(x) && IS_SPEC_OBJECT(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_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); | 159 return IS_SPEC_OBJECT(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_SPEC_OBJECT(x)) { | 168 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x)); |
198 // 25.4.1.3.2 steps 8-12 | 169 } |
199 try { | 170 if (IS_SPEC_OBJECT(x)) { |
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; |
203 } | 174 } catch (e) { |
204 if (IS_CALLABLE(then)) { | 175 return PromiseReject(promise, e); |
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 } | 176 } |
216 PromiseDone(promise, +1, x, promiseOnResolveSymbol); | 177 if (IS_CALLABLE(then)) { |
| 178 // PromiseResolveThenableJob |
| 179 return %EnqueueMicrotask(function() { |
| 180 try { |
| 181 var callbacks = CreateResolvingFunctions(promise); |
| 182 %_Call(then, x, callbacks.resolve, callbacks.reject); |
| 183 } catch (e) { |
| 184 PromiseReject(promise, e); |
| 185 } |
| 186 }); |
| 187 } |
217 } | 188 } |
| 189 PromiseDone(promise, +1, x, promiseOnResolveSymbol); |
218 } | 190 } |
219 | 191 |
220 function PromiseReject(promise, r) { | 192 function PromiseReject(promise, r) { |
221 // Check promise status to confirm that this reject has an effect. | 193 // Check promise status to confirm that this reject has an effect. |
222 // Call runtime for callbacks to the debugger or for unhandled reject. | 194 // Call runtime for callbacks to the debugger or for unhandled reject. |
223 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { | 195 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { |
224 var debug_is_active = DEBUG_IS_ACTIVE; | 196 var debug_is_active = DEBUG_IS_ACTIVE; |
225 if (debug_is_active || | 197 if (debug_is_active || |
226 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { | 198 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { |
227 %PromiseRejectEvent(promise, r, debug_is_active); | 199 %PromiseRejectEvent(promise, r, debug_is_active); |
(...skipping 10 matching lines...) Expand all Loading... |
238 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); | 210 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); |
239 var callbacks = CreateResolvingFunctions(promise); | 211 var callbacks = CreateResolvingFunctions(promise); |
240 return { | 212 return { |
241 promise: promise, | 213 promise: promise, |
242 resolve: callbacks.resolve, | 214 resolve: callbacks.resolve, |
243 reject: callbacks.reject | 215 reject: callbacks.reject |
244 }; | 216 }; |
245 } else { | 217 } else { |
246 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; | 218 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; |
247 result.promise = new C(function(resolve, reject) { | 219 result.promise = new C(function(resolve, reject) { |
| 220 // TODO(littledan): Check for resolve and reject being not undefined |
248 result.resolve = resolve; | 221 result.resolve = resolve; |
249 result.reject = reject; | 222 result.reject = reject; |
250 }); | 223 }); |
251 return result; | 224 return result; |
252 } | 225 } |
253 } | 226 } |
254 | 227 |
255 function PromiseDeferred() { | 228 function PromiseDeferred() { |
256 return NewPromiseCapability(this); | 229 return NewPromiseCapability(this); |
257 } | 230 } |
258 | 231 |
259 function PromiseResolved(x) { | 232 function PromiseResolved(x) { |
260 if (this === GlobalPromise) { | 233 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 } | 234 } |
267 | 235 |
268 function PromiseRejected(r) { | 236 function PromiseRejected(r) { |
| 237 if (!IS_SPEC_OBJECT(this)) { |
| 238 throw MakeTypeError(kCalledOnNonObject, PromiseRejected); |
| 239 } |
269 var promise; | 240 var promise; |
270 if (this === GlobalPromise) { | 241 if (this === GlobalPromise) { |
271 // Optimized case, avoid extra closure. | 242 // Optimized case, avoid extra closure. |
272 promise = PromiseCreateAndSet(-1, r); | 243 promise = PromiseCreateAndSet(-1, r); |
273 // The debug event for this would always be an uncaught promise reject, | 244 // The debug event for this would always be an uncaught promise reject, |
274 // which is usually simply noise. Do not trigger that debug event. | 245 // which is usually simply noise. Do not trigger that debug event. |
275 %PromiseRejectEvent(promise, r, false); | 246 %PromiseRejectEvent(promise, r, false); |
276 } else { | 247 } else { |
277 promise = new this(function(resolve, reject) { reject(r) }); | 248 promise = new this(function(resolve, reject) { reject(r) }); |
278 } | 249 } |
279 return promise; | 250 return promise; |
280 } | 251 } |
281 | 252 |
282 // Simple chaining. | 253 // Multi-unwrapped chaining with thenable coercion. |
283 | 254 |
284 // PromiseChain a.k.a. flatMap | 255 function PromiseThen(onResolve, onReject) { |
285 function PromiseChainInternal(constructor, onResolve, onReject) { | 256 var constructor = this.constructor; |
286 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; | 257 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; |
287 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; | 258 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; |
288 var deferred = NewPromiseCapability(constructor); | 259 var deferred = NewPromiseCapability(constructor); |
289 switch (GET_PRIVATE(this, promiseStatusSymbol)) { | 260 switch (GET_PRIVATE(this, promiseStatusSymbol)) { |
290 case UNDEFINED: | 261 case UNDEFINED: |
| 262 // TODO(littledan): The type check should be called before |
| 263 // constructing NewPromiseCapability; this is observable when |
| 264 // erroneously copying this method to other classes. |
291 throw MakeTypeError(kNotAPromise, this); | 265 throw MakeTypeError(kNotAPromise, this); |
292 case 0: // Pending | 266 case 0: // Pending |
293 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); | 267 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); |
294 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); | 268 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); |
295 break; | 269 break; |
296 case +1: // Resolved | 270 case +1: // Resolved |
297 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), | 271 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), |
298 [onResolve, deferred], | 272 [onResolve, deferred], |
299 +1); | 273 +1); |
300 break; | 274 break; |
301 case -1: // Rejected | 275 case -1: // Rejected |
302 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { | 276 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { |
303 // Promise has already been rejected, but had no handler. | 277 // Promise has already been rejected, but had no handler. |
304 // Revoke previously triggered reject event. | 278 // Revoke previously triggered reject event. |
305 %PromiseRevokeReject(this); | 279 %PromiseRevokeReject(this); |
306 } | 280 } |
307 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), | 281 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), |
308 [onReject, deferred], | 282 [onReject, deferred], |
309 -1); | 283 -1); |
310 break; | 284 break; |
311 } | 285 } |
312 // Mark this promise as having handler. | 286 // Mark this promise as having handler. |
313 SET_PRIVATE(this, promiseHasHandlerSymbol, true); | 287 SET_PRIVATE(this, promiseHasHandlerSymbol, true); |
314 if (DEBUG_IS_ACTIVE) { | 288 if (DEBUG_IS_ACTIVE) { |
315 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); | 289 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); |
316 } | 290 } |
317 return deferred.promise; | 291 return deferred.promise; |
318 } | 292 } |
319 | 293 |
| 294 // Chain is left around for now as an alias for then |
320 function PromiseChain(onResolve, onReject) { | 295 function PromiseChain(onResolve, onReject) { |
321 return %_Call(PromiseChainInternal, this, this.constructor, | 296 return %_Call(PromiseThen, this, onResolve, onReject); |
322 onResolve, onReject); | |
323 } | 297 } |
324 | 298 |
325 function PromiseCatch(onReject) { | 299 function PromiseCatch(onReject) { |
326 return this.then(UNDEFINED, onReject); | 300 return this.then(UNDEFINED, onReject); |
327 } | 301 } |
328 | 302 |
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. | 303 // Combinators. |
355 | 304 |
356 function PromiseCast(x) { | 305 function PromiseCast(x) { |
| 306 if (!IS_SPEC_OBJECT(this)) { |
| 307 throw MakeTypeError(kCalledOnNonObject, PromiseCast); |
| 308 } |
357 if (IsPromise(x) && x.constructor === this) { | 309 if (IsPromise(x) && x.constructor === this) { |
358 return x; | 310 return x; |
359 } else { | 311 } else { |
360 return new this(function(resolve) { resolve(x) }); | 312 return new this(function(resolve, reject) { resolve(x) }); |
361 } | 313 } |
362 } | 314 } |
363 | 315 |
364 function PromiseAll(iterable) { | 316 function PromiseAll(iterable) { |
365 var deferred = NewPromiseCapability(this); | 317 var deferred = NewPromiseCapability(this); |
366 var resolutions = []; | 318 var resolutions = []; |
367 try { | 319 try { |
368 var count = 0; | 320 var count = 0; |
369 var i = 0; | 321 var i = 0; |
370 for (var value of iterable) { | 322 for (var value of iterable) { |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
477 [PromiseChain, PromiseDeferred, PromiseResolved].forEach( | 429 [PromiseChain, PromiseDeferred, PromiseResolved].forEach( |
478 fn => %FunctionRemovePrototype(fn)); | 430 fn => %FunctionRemovePrototype(fn)); |
479 | 431 |
480 utils.Export(function(to) { | 432 utils.Export(function(to) { |
481 to.PromiseChain = PromiseChain; | 433 to.PromiseChain = PromiseChain; |
482 to.PromiseDeferred = PromiseDeferred; | 434 to.PromiseDeferred = PromiseDeferred; |
483 to.PromiseResolved = PromiseResolved; | 435 to.PromiseResolved = PromiseResolved; |
484 }); | 436 }); |
485 | 437 |
486 }) | 438 }) |
OLD | NEW |