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_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)) { |
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)) { |
| 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) { |
| 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); |
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) { |
| 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 |