Chromium Code Reviews| 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 |
| 11 // ------------------------------------------------------------------- | 11 // ------------------------------------------------------------------- |
| 12 // Imports | 12 // Imports |
| 13 | 13 |
| 14 var InternalArray = utils.InternalArray; | 14 var InternalArray = utils.InternalArray; |
| 15 var MakeTypeError; | 15 var MakeTypeError; |
| 16 var promiseCombinedDeferredSymbol = | 16 var promiseCombinedDeferredSymbol = |
| 17 utils.ImportNow("promise_combined_deferred_symbol"); | 17 utils.ImportNow("promise_combined_deferred_symbol"); |
| 18 var promiseHasHandlerSymbol = | 18 var promiseHasHandlerSymbol = |
| 19 utils.ImportNow("promise_has_handler_symbol"); | 19 utils.ImportNow("promise_has_handler_symbol"); |
| 20 var promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol"); | 20 var promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol"); |
| 21 var promiseOnResolveSymbol = | 21 var promiseOnResolveSymbol = |
| 22 utils.ImportNow("promise_on_resolve_symbol"); | 22 utils.ImportNow("promise_on_resolve_symbol"); |
| 23 var alreadyResolvedSymbol = utils.ImportNow("promise_already_resolved_symbol"); | |
| 23 var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); | 24 var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); |
| 24 var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); | 25 var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); |
| 25 var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); | 26 var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); |
| 26 var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); | 27 var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); |
| 27 | 28 |
| 28 utils.Import(function(from) { | 29 utils.Import(function(from) { |
| 29 MakeTypeError = from.MakeTypeError; | 30 MakeTypeError = from.MakeTypeError; |
| 30 }); | 31 }); |
| 31 | 32 |
| 32 // ------------------------------------------------------------------- | 33 // ------------------------------------------------------------------- |
| 33 | 34 |
| 34 // Status values: 0 = pending, +1 = resolved, -1 = rejected | 35 // Status values: 0 = pending, +1 = resolved, -1 = rejected |
| 35 var lastMicrotaskId = 0; | 36 var lastMicrotaskId = 0; |
| 36 | 37 |
| 38 function CreateResolvingFunctions(promise) { | |
|
caitp (gmail)
2015/11/09 21:10:24
http://tc39.github.io/ecma262/#sec-createresolving
| |
| 39 var alreadyResolved = { __proto__: null, value: false }; | |
| 40 | |
| 41 var resolve = function(value) { | |
|
caitp (gmail)
2015/11/09 21:10:24
I think the spec mandates that these functions don
| |
| 42 var alreadyResolved = GET_PRIVATE(resolve, alreadyResolvedSymbol); | |
| 43 if (alreadyResolved.value === true) return; | |
| 44 alreadyResolved.value = true; | |
| 45 var promise = GET_PRIVATE(resolve, promiseValueSymbol); | |
| 46 if (value === promise) { | |
| 47 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value)); | |
| 48 } | |
| 49 PromiseResolve(promise, value); | |
| 50 }; | |
| 51 | |
| 52 var reject = function(reason) { | |
| 53 if (alreadyResolved.value === true) return; | |
| 54 alreadyResolved.value = true; | |
| 55 PromiseReject(GET_PRIVATE(reject, promiseValueSymbol), reason); | |
| 56 }; | |
| 57 | |
| 58 SET_PRIVATE(resolve, promiseValueSymbol, promise); | |
| 59 SET_PRIVATE(resolve, alreadyResolvedSymbol, alreadyResolved); | |
| 60 SET_PRIVATE(reject, promiseValueSymbol, promise); | |
| 61 SET_PRIVATE(reject, alreadyResolvedSymbol, alreadyResolved); | |
| 62 return { | |
| 63 resolve: resolve, | |
| 64 reject: reject | |
| 65 }; | |
| 66 } | |
| 67 | |
| 68 | |
| 37 var GlobalPromise = function Promise(resolver) { | 69 var GlobalPromise = function Promise(resolver) { |
| 38 if (resolver === promiseRawSymbol) return; | 70 if (resolver === promiseRawSymbol) return; |
| 39 if (!%_IsConstructCall()) throw MakeTypeError(kNotAPromise, this); | 71 if (!%_IsConstructCall()) throw MakeTypeError(kNotAPromise, this); |
| 40 if (!IS_CALLABLE(resolver)) | 72 if (!IS_CALLABLE(resolver)) |
| 41 throw MakeTypeError(kResolverNotAFunction, resolver); | 73 throw MakeTypeError(kResolverNotAFunction, resolver); |
| 42 var promise = PromiseInit(this); | 74 var promise = PromiseInit(this); |
| 43 try { | 75 try { |
| 44 %DebugPushPromise(promise, Promise, resolver); | 76 %DebugPushPromise(promise, Promise, resolver); |
| 45 resolver(function(x) { PromiseResolve(promise, x) }, | 77 var callbacks = CreateResolvingFunctions(promise); |
| 46 function(r) { PromiseReject(promise, r) }); | 78 resolver(callbacks.resolve, callbacks.reject); |
| 47 } catch (e) { | 79 } catch (e) { |
| 48 PromiseReject(promise, e); | 80 PromiseReject(promise, e); |
| 49 } finally { | 81 } finally { |
| 50 %DebugPopPromise(); | 82 %DebugPopPromise(); |
| 51 } | 83 } |
| 52 } | 84 } |
| 53 | 85 |
| 54 // Core functionality. | 86 // Core functionality. |
| 55 | 87 |
| 56 function PromiseSet(promise, status, value, onResolve, onReject) { | 88 function PromiseSet(promise, status, value, onResolve, onReject) { |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 86 | 118 |
| 87 function PromiseCoerce(constructor, x) { | 119 function PromiseCoerce(constructor, x) { |
| 88 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { | 120 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { |
| 89 var then; | 121 var then; |
| 90 try { | 122 try { |
| 91 then = x.then; | 123 then = x.then; |
| 92 } catch(r) { | 124 } catch(r) { |
| 93 return %_Call(PromiseRejected, constructor, r); | 125 return %_Call(PromiseRejected, constructor, r); |
| 94 } | 126 } |
| 95 if (IS_CALLABLE(then)) { | 127 if (IS_CALLABLE(then)) { |
| 96 var deferred = %_Call(PromiseDeferred, constructor); | 128 var deferred = NewPromiseCapability(constructor); |
| 97 try { | 129 try { |
| 98 %_Call(then, x, deferred.resolve, deferred.reject); | 130 %_Call(then, x, deferred.resolve, deferred.reject); |
| 99 } catch(r) { | 131 } catch(r) { |
| 100 deferred.reject(r); | 132 deferred.reject(r); |
| 101 } | 133 } |
| 102 return deferred.promise; | 134 return deferred.promise; |
| 103 } | 135 } |
| 104 } | 136 } |
| 105 return x; | 137 return x; |
| 106 } | 138 } |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 154 | 186 |
| 155 function IsPromise(x) { | 187 function IsPromise(x) { |
| 156 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); | 188 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); |
| 157 } | 189 } |
| 158 | 190 |
| 159 function PromiseCreate() { | 191 function PromiseCreate() { |
| 160 return new GlobalPromise(PromiseNopResolver) | 192 return new GlobalPromise(PromiseNopResolver) |
| 161 } | 193 } |
| 162 | 194 |
| 163 function PromiseResolve(promise, x) { | 195 function PromiseResolve(promise, x) { |
| 164 PromiseDone(promise, +1, x, promiseOnResolveSymbol) | 196 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { |
| 197 if (IS_SPEC_OBJECT(x)) { | |
| 198 // 25.4.1.3.2 steps 8-12 | |
| 199 try { | |
| 200 var then = x.then; | |
| 201 } catch (e) { | |
| 202 return PromiseReject(promise, e); | |
| 203 } | |
| 204 if (IS_CALLABLE(then)) { | |
| 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 } | |
| 216 PromiseDone(promise, +1, x, promiseOnResolveSymbol); | |
| 217 } | |
| 165 } | 218 } |
| 166 | 219 |
| 167 function PromiseReject(promise, r) { | 220 function PromiseReject(promise, r) { |
| 168 // Check promise status to confirm that this reject has an effect. | 221 // Check promise status to confirm that this reject has an effect. |
| 169 // Call runtime for callbacks to the debugger or for unhandled reject. | 222 // Call runtime for callbacks to the debugger or for unhandled reject. |
| 170 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { | 223 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { |
| 171 var debug_is_active = DEBUG_IS_ACTIVE; | 224 var debug_is_active = DEBUG_IS_ACTIVE; |
| 172 if (debug_is_active || | 225 if (debug_is_active || |
| 173 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { | 226 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { |
| 174 %PromiseRejectEvent(promise, r, debug_is_active); | 227 %PromiseRejectEvent(promise, r, debug_is_active); |
| 175 } | 228 } |
| 176 } | 229 } |
| 177 PromiseDone(promise, -1, r, promiseOnRejectSymbol) | 230 PromiseDone(promise, -1, r, promiseOnRejectSymbol) |
| 178 } | 231 } |
| 179 | 232 |
| 180 // Convenience. | 233 // Convenience. |
| 181 | 234 |
| 182 function PromiseDeferred() { | 235 function NewPromiseCapability(C) { |
|
caitp (gmail)
2015/11/09 21:10:24
Sort-of (but not quite) matches the spec concept "
| |
| 183 if (this === GlobalPromise) { | 236 if (C === GlobalPromise) { |
| 184 // Optimized case, avoid extra closure. | 237 // Optimized case, avoid extra closure. |
| 185 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); | 238 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); |
| 239 var callbacks = CreateResolvingFunctions(promise); | |
| 186 return { | 240 return { |
| 187 promise: promise, | 241 promise: promise, |
| 188 resolve: function(x) { PromiseResolve(promise, x) }, | 242 resolve: callbacks.resolve, |
| 189 reject: function(r) { PromiseReject(promise, r) } | 243 reject: callbacks.reject |
| 190 }; | 244 }; |
| 191 } else { | 245 } else { |
| 192 var result = {promise: UNDEFINED, reject: UNDEFINED, resolve: UNDEFINED}; | 246 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; |
| 193 result.promise = new this(function(resolve, reject) { | 247 result.promise = new C(function(resolve, reject) { |
| 194 result.resolve = resolve; | 248 result.resolve = resolve; |
| 195 result.reject = reject; | 249 result.reject = reject; |
| 196 }); | 250 }); |
| 197 return result; | 251 return result; |
| 198 } | 252 } |
| 199 } | 253 } |
| 200 | 254 |
| 255 function PromiseDeferred() { | |
| 256 return NewPromiseCapability(this); | |
| 257 } | |
| 258 | |
| 201 function PromiseResolved(x) { | 259 function PromiseResolved(x) { |
| 202 if (this === GlobalPromise) { | 260 if (this === GlobalPromise) { |
| 203 // Optimized case, avoid extra closure. | 261 // Optimized case, avoid extra closure. |
| 204 return PromiseCreateAndSet(+1, x); | 262 return PromiseCreateAndSet(+1, x); |
| 205 } else { | 263 } else { |
| 206 return new this(function(resolve, reject) { resolve(x) }); | 264 return new this(function(resolve, reject) { resolve(x) }); |
| 207 } | 265 } |
| 208 } | 266 } |
| 209 | 267 |
| 210 function PromiseRejected(r) { | 268 function PromiseRejected(r) { |
| 211 var promise; | 269 var promise; |
| 212 if (this === GlobalPromise) { | 270 if (this === GlobalPromise) { |
| 213 // Optimized case, avoid extra closure. | 271 // Optimized case, avoid extra closure. |
| 214 promise = PromiseCreateAndSet(-1, r); | 272 promise = PromiseCreateAndSet(-1, r); |
| 215 // The debug event for this would always be an uncaught promise reject, | 273 // The debug event for this would always be an uncaught promise reject, |
| 216 // which is usually simply noise. Do not trigger that debug event. | 274 // which is usually simply noise. Do not trigger that debug event. |
| 217 %PromiseRejectEvent(promise, r, false); | 275 %PromiseRejectEvent(promise, r, false); |
| 218 } else { | 276 } else { |
| 219 promise = new this(function(resolve, reject) { reject(r) }); | 277 promise = new this(function(resolve, reject) { reject(r) }); |
| 220 } | 278 } |
| 221 return promise; | 279 return promise; |
| 222 } | 280 } |
| 223 | 281 |
| 224 // Simple chaining. | 282 // Simple chaining. |
| 225 | 283 |
| 226 function PromiseChain(onResolve, onReject) { // a.k.a. flatMap | 284 // PromiseChain a.k.a. flatMap |
| 285 function PromiseChainInternal(constructor, onResolve, onReject) { | |
| 227 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; | 286 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
| 228 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; | 287 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; |
| 229 var deferred = %_Call(PromiseDeferred, this.constructor); | 288 var deferred = NewPromiseCapability(constructor); |
| 230 switch (GET_PRIVATE(this, promiseStatusSymbol)) { | 289 switch (GET_PRIVATE(this, promiseStatusSymbol)) { |
| 231 case UNDEFINED: | 290 case UNDEFINED: |
| 232 throw MakeTypeError(kNotAPromise, this); | 291 throw MakeTypeError(kNotAPromise, this); |
| 233 case 0: // Pending | 292 case 0: // Pending |
| 234 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); | 293 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); |
| 235 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); | 294 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); |
| 236 break; | 295 break; |
| 237 case +1: // Resolved | 296 case +1: // Resolved |
| 238 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), | 297 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), |
| 239 [onResolve, deferred], | 298 [onResolve, deferred], |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 251 break; | 310 break; |
| 252 } | 311 } |
| 253 // Mark this promise as having handler. | 312 // Mark this promise as having handler. |
| 254 SET_PRIVATE(this, promiseHasHandlerSymbol, true); | 313 SET_PRIVATE(this, promiseHasHandlerSymbol, true); |
| 255 if (DEBUG_IS_ACTIVE) { | 314 if (DEBUG_IS_ACTIVE) { |
| 256 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); | 315 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); |
| 257 } | 316 } |
| 258 return deferred.promise; | 317 return deferred.promise; |
| 259 } | 318 } |
| 260 | 319 |
| 320 function PromiseChain(onResolve, onReject) { | |
| 321 return %_Call(PromiseChainInternal, this, this.constructor, | |
|
caitp (gmail)
2015/11/09 21:10:24
I think the goal is to get rid of this, but it's b
| |
| 322 onResolve, onReject); | |
| 323 } | |
| 324 | |
| 261 function PromiseCatch(onReject) { | 325 function PromiseCatch(onReject) { |
| 262 return this.then(UNDEFINED, onReject); | 326 return this.then(UNDEFINED, onReject); |
| 263 } | 327 } |
| 264 | 328 |
| 265 // Multi-unwrapped chaining with thenable coercion. | 329 // Multi-unwrapped chaining with thenable coercion. |
| 266 | 330 |
| 267 function PromiseThen(onResolve, onReject) { | 331 function PromiseThen(onResolve, onReject) { |
| 268 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; | 332 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; |
| 269 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; | 333 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; |
| 270 var that = this; | 334 var that = this; |
| 271 var constructor = this.constructor; | 335 var constructor = this.constructor; |
| 272 return %_Call( | 336 return %_Call( |
| 273 PromiseChain, | 337 PromiseChainInternal, |
| 274 this, | 338 this, |
| 339 constructor, | |
| 275 function(x) { | 340 function(x) { |
| 276 x = PromiseCoerce(constructor, x); | 341 x = PromiseCoerce(constructor, x); |
| 277 if (x === that) { | 342 if (x === that) { |
| 278 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); | 343 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); |
| 279 return onReject(MakeTypeError(kPromiseCyclic, x)); | 344 return onReject(MakeTypeError(kPromiseCyclic, x)); |
| 280 } else if (IsPromise(x)) { | 345 } else if (IsPromise(x)) { |
| 281 return x.then(onResolve, onReject); | 346 return x.then(onResolve, onReject); |
| 282 } else { | 347 } else { |
| 283 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); | 348 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); |
| 284 return onResolve(x); | 349 return onResolve(x); |
| 285 } | 350 } |
| 286 }, | 351 }, |
| 287 onReject | 352 onReject |
| 288 ); | 353 ); |
| 289 } | 354 } |
| 290 | 355 |
| 291 // Combinators. | 356 // Combinators. |
| 292 | 357 |
| 293 function PromiseCast(x) { | 358 function PromiseCast(x) { |
| 294 if (IsPromise(x) && x.constructor === this) { | 359 if (IsPromise(x) && x.constructor === this) { |
| 295 return x; | 360 return x; |
| 296 } else { | 361 } else { |
| 297 return new this(function(resolve) { resolve(x) }); | 362 return new this(function(resolve) { resolve(x) }); |
| 298 } | 363 } |
| 299 } | 364 } |
| 300 | 365 |
| 301 function PromiseAll(iterable) { | 366 function PromiseAll(iterable) { |
| 302 var deferred = %_Call(PromiseDeferred, this); | 367 var deferred = NewPromiseCapability(this); |
| 303 var resolutions = []; | 368 var resolutions = []; |
| 304 try { | 369 try { |
| 305 var count = 0; | 370 var count = 0; |
| 306 var i = 0; | 371 var i = 0; |
| 307 for (var value of iterable) { | 372 for (var value of iterable) { |
| 308 var reject = function(r) { deferred.reject(r) }; | 373 var reject = function(r) { deferred.reject(r) }; |
| 309 this.resolve(value).then( | 374 this.resolve(value).then( |
| 310 // Nested scope to get closure over current i. | 375 // Nested scope to get closure over current i. |
| 311 // TODO(arv): Use an inner let binding once available. | 376 // TODO(arv): Use an inner let binding once available. |
| 312 (function(i) { | 377 (function(i) { |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 324 deferred.resolve(resolutions); | 389 deferred.resolve(resolutions); |
| 325 } | 390 } |
| 326 | 391 |
| 327 } catch (e) { | 392 } catch (e) { |
| 328 deferred.reject(e) | 393 deferred.reject(e) |
| 329 } | 394 } |
| 330 return deferred.promise; | 395 return deferred.promise; |
| 331 } | 396 } |
| 332 | 397 |
| 333 function PromiseRace(iterable) { | 398 function PromiseRace(iterable) { |
| 334 var deferred = %_Call(PromiseDeferred, this); | 399 var deferred = NewPromiseCapability(this); |
| 335 try { | 400 try { |
| 336 for (var value of iterable) { | 401 for (var value of iterable) { |
| 337 var reject = function(r) { deferred.reject(r) }; | 402 var reject = function(r) { deferred.reject(r) }; |
| 338 this.resolve(value).then(function(x) { deferred.resolve(x) }, reject); | 403 this.resolve(value).then(function(x) { deferred.resolve(x) }, reject); |
| 339 SET_PRIVATE(reject, promiseCombinedDeferredSymbol, deferred); | 404 SET_PRIVATE(reject, promiseCombinedDeferredSymbol, deferred); |
| 340 } | 405 } |
| 341 } catch (e) { | 406 } catch (e) { |
| 342 deferred.reject(e) | 407 deferred.reject(e) |
| 343 } | 408 } |
| 344 return deferred.promise; | 409 return deferred.promise; |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 408 // This allows extras to create promises quickly without building extra | 473 // This allows extras to create promises quickly without building extra |
| 409 // resolve/reject closures, and allows them to later resolve and reject any | 474 // resolve/reject closures, and allows them to later resolve and reject any |
| 410 // promise without having to hold on to those closures forever. | 475 // promise without having to hold on to those closures forever. |
| 411 utils.InstallFunctions(extrasUtils, 0, [ | 476 utils.InstallFunctions(extrasUtils, 0, [ |
| 412 "createPromise", PromiseCreate, | 477 "createPromise", PromiseCreate, |
| 413 "resolvePromise", PromiseResolve, | 478 "resolvePromise", PromiseResolve, |
| 414 "rejectPromise", PromiseReject | 479 "rejectPromise", PromiseReject |
| 415 ]); | 480 ]); |
| 416 | 481 |
| 417 }) | 482 }) |
| OLD | NEW |