Index: src/js/promise.js |
diff --git a/src/js/promise.js b/src/js/promise.js |
index d7e9a5c67fc069f0b3099b4bd2aee04799ea2f39..d84d70df4ec8eadcb700c260117759ebeac2195f 100644 |
--- a/src/js/promise.js |
+++ b/src/js/promise.js |
@@ -34,6 +34,32 @@ utils.Import(function(from) { |
// Status values: 0 = pending, +1 = resolved, -1 = rejected |
var lastMicrotaskId = 0; |
+function CreateResolvingFunctions(promise) { |
+ var alreadyResolved = false; |
+ |
+ var resolve = function(value) { |
+ if (alreadyResolved === true) return; |
+ alreadyResolved = true; |
+ if (value === promise) { |
+ return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value)); |
+ } |
+ PromiseResolve(promise, value); |
+ }; |
+ |
+ var reject = function(reason) { |
+ if (alreadyResolved === true) return; |
+ alreadyResolved = true; |
+ PromiseReject(promise, reason); |
+ }; |
+ |
+ return { |
+ __proto__: null, |
+ resolve: resolve, |
+ reject: reject |
+ }; |
+} |
+ |
+ |
var GlobalPromise = function Promise(resolver) { |
if (resolver === promiseRawSymbol) return; |
if (!%_IsConstructCall()) throw MakeTypeError(kNotAPromise, this); |
@@ -42,8 +68,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 +119,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 +187,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 +226,19 @@ function PromiseReject(promise, r) { |
// Convenience. |
-function PromiseDeferred() { |
- if (this === GlobalPromise) { |
+function NewPromiseCapability(C) { |
+ 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 +246,10 @@ function PromiseDeferred() { |
} |
} |
+function PromiseDeferred() { |
+ return NewPromiseCapability(this); |
+} |
+ |
function PromiseResolved(x) { |
if (this === GlobalPromise) { |
// Optimized case, avoid extra closure. |
@@ -223,10 +275,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 +311,11 @@ function PromiseChain(onResolve, onReject) { // a.k.a. flatMap |
return deferred.promise; |
} |
+function PromiseChain(onResolve, onReject) { |
+ return %_Call(PromiseChainInternal, this, this.constructor, |
+ onResolve, onReject); |
+} |
+ |
function PromiseCatch(onReject) { |
return this.then(UNDEFINED, onReject); |
} |
@@ -270,8 +328,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 +358,7 @@ function PromiseCast(x) { |
} |
function PromiseAll(iterable) { |
- var deferred = %_Call(PromiseDeferred, this); |
+ var deferred = NewPromiseCapability(this); |
var resolutions = []; |
try { |
var count = 0; |
@@ -331,7 +390,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) }; |