Chromium Code Reviews| Index: src/js/promise.js |
| diff --git a/src/js/promise.js b/src/js/promise.js |
| index d7e9a5c67fc069f0b3099b4bd2aee04799ea2f39..4b859fa36dcf682f6adbbe3995361b6508fe6ba0 100644 |
| --- a/src/js/promise.js |
| +++ b/src/js/promise.js |
| @@ -20,6 +20,7 @@ var promiseHasHandlerSymbol = |
| var promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol"); |
| var promiseOnResolveSymbol = |
| utils.ImportNow("promise_on_resolve_symbol"); |
| +var alreadyResolvedSymbol = utils.ImportNow("promise_already_resolved_symbol"); |
| var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); |
| var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); |
| var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); |
| @@ -34,6 +35,37 @@ utils.Import(function(from) { |
| // Status values: 0 = pending, +1 = resolved, -1 = rejected |
| var lastMicrotaskId = 0; |
| +function CreateResolvingFunctions(promise) { |
|
caitp (gmail)
2015/11/09 21:10:24
http://tc39.github.io/ecma262/#sec-createresolving
|
| + var alreadyResolved = { __proto__: null, value: false }; |
| + |
| + var resolve = function(value) { |
|
caitp (gmail)
2015/11/09 21:10:24
I think the spec mandates that these functions don
|
| + var alreadyResolved = GET_PRIVATE(resolve, alreadyResolvedSymbol); |
| + if (alreadyResolved.value === true) return; |
| + alreadyResolved.value = true; |
| + var promise = GET_PRIVATE(resolve, promiseValueSymbol); |
| + if (value === promise) { |
| + return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value)); |
| + } |
| + PromiseResolve(promise, value); |
| + }; |
| + |
| + var reject = function(reason) { |
| + if (alreadyResolved.value === true) return; |
| + alreadyResolved.value = true; |
| + PromiseReject(GET_PRIVATE(reject, promiseValueSymbol), reason); |
| + }; |
| + |
| + SET_PRIVATE(resolve, promiseValueSymbol, promise); |
| + SET_PRIVATE(resolve, alreadyResolvedSymbol, alreadyResolved); |
| + SET_PRIVATE(reject, promiseValueSymbol, promise); |
| + SET_PRIVATE(reject, alreadyResolvedSymbol, alreadyResolved); |
| + return { |
| + resolve: resolve, |
| + reject: reject |
| + }; |
| +} |
| + |
| + |
| var GlobalPromise = function Promise(resolver) { |
| if (resolver === promiseRawSymbol) return; |
| if (!%_IsConstructCall()) throw MakeTypeError(kNotAPromise, this); |
| @@ -42,8 +74,8 @@ var GlobalPromise = function Promise(resolver) { |
| var promise = PromiseInit(this); |
| try { |
| %DebugPushPromise(promise, Promise, resolver); |
| - resolver(function(x) { PromiseResolve(promise, x) }, |
| - function(r) { PromiseReject(promise, r) }); |
| + var callbacks = CreateResolvingFunctions(promise); |
| + resolver(callbacks.resolve, callbacks.reject); |
| } catch (e) { |
| PromiseReject(promise, e); |
| } finally { |
| @@ -93,7 +125,7 @@ function PromiseCoerce(constructor, x) { |
| return %_Call(PromiseRejected, constructor, r); |
| } |
| if (IS_CALLABLE(then)) { |
| - var deferred = %_Call(PromiseDeferred, constructor); |
| + var deferred = NewPromiseCapability(constructor); |
| try { |
| %_Call(then, x, deferred.resolve, deferred.reject); |
| } catch(r) { |
| @@ -161,7 +193,28 @@ function PromiseCreate() { |
| } |
| function PromiseResolve(promise, x) { |
| - PromiseDone(promise, +1, x, promiseOnResolveSymbol) |
| + if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { |
| + if (IS_SPEC_OBJECT(x)) { |
| + // 25.4.1.3.2 steps 8-12 |
| + try { |
| + var then = x.then; |
| + } catch (e) { |
| + return PromiseReject(promise, e); |
| + } |
| + if (IS_CALLABLE(then)) { |
| + // PromiseResolveThenableJob |
| + return %EnqueueMicrotask(function() { |
| + try { |
| + var callbacks = CreateResolvingFunctions(promise); |
| + %_Call(then, x, callbacks.resolve, callbacks.reject); |
| + } catch (e) { |
| + PromiseReject(promise, e); |
| + } |
| + }); |
| + } |
| + } |
| + PromiseDone(promise, +1, x, promiseOnResolveSymbol); |
| + } |
| } |
| function PromiseReject(promise, r) { |
| @@ -179,18 +232,19 @@ function PromiseReject(promise, r) { |
| // Convenience. |
| -function PromiseDeferred() { |
| - if (this === GlobalPromise) { |
| +function NewPromiseCapability(C) { |
|
caitp (gmail)
2015/11/09 21:10:24
Sort-of (but not quite) matches the spec concept "
|
| + if (C === GlobalPromise) { |
| // Optimized case, avoid extra closure. |
| var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); |
| + var callbacks = CreateResolvingFunctions(promise); |
| return { |
| promise: promise, |
| - resolve: function(x) { PromiseResolve(promise, x) }, |
| - reject: function(r) { PromiseReject(promise, r) } |
| + resolve: callbacks.resolve, |
| + reject: callbacks.reject |
| }; |
| } else { |
| - var result = {promise: UNDEFINED, reject: UNDEFINED, resolve: UNDEFINED}; |
| - result.promise = new this(function(resolve, reject) { |
| + var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; |
| + result.promise = new C(function(resolve, reject) { |
| result.resolve = resolve; |
| result.reject = reject; |
| }); |
| @@ -198,6 +252,10 @@ function PromiseDeferred() { |
| } |
| } |
| +function PromiseDeferred() { |
| + return NewPromiseCapability(this); |
| +} |
| + |
| function PromiseResolved(x) { |
| if (this === GlobalPromise) { |
| // Optimized case, avoid extra closure. |
| @@ -223,10 +281,11 @@ function PromiseRejected(r) { |
| // Simple chaining. |
| -function PromiseChain(onResolve, onReject) { // a.k.a. flatMap |
| +// PromiseChain a.k.a. flatMap |
| +function PromiseChainInternal(constructor, onResolve, onReject) { |
| onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
| onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; |
| - var deferred = %_Call(PromiseDeferred, this.constructor); |
| + var deferred = NewPromiseCapability(constructor); |
| switch (GET_PRIVATE(this, promiseStatusSymbol)) { |
| case UNDEFINED: |
| throw MakeTypeError(kNotAPromise, this); |
| @@ -258,6 +317,11 @@ function PromiseChain(onResolve, onReject) { // a.k.a. flatMap |
| return deferred.promise; |
| } |
| +function PromiseChain(onResolve, onReject) { |
| + 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
|
| + onResolve, onReject); |
| +} |
| + |
| function PromiseCatch(onReject) { |
| return this.then(UNDEFINED, onReject); |
| } |
| @@ -270,8 +334,9 @@ function PromiseThen(onResolve, onReject) { |
| var that = this; |
| var constructor = this.constructor; |
| return %_Call( |
| - PromiseChain, |
| + PromiseChainInternal, |
| this, |
| + constructor, |
| function(x) { |
| x = PromiseCoerce(constructor, x); |
| if (x === that) { |
| @@ -299,7 +364,7 @@ function PromiseCast(x) { |
| } |
| function PromiseAll(iterable) { |
| - var deferred = %_Call(PromiseDeferred, this); |
| + var deferred = NewPromiseCapability(this); |
| var resolutions = []; |
| try { |
| var count = 0; |
| @@ -331,7 +396,7 @@ function PromiseAll(iterable) { |
| } |
| function PromiseRace(iterable) { |
| - var deferred = %_Call(PromiseDeferred, this); |
| + var deferred = NewPromiseCapability(this); |
| try { |
| for (var value of iterable) { |
| var reject = function(r) { deferred.reject(r) }; |