Index: src/js/promise.js |
diff --git a/src/js/promise.js b/src/js/promise.js |
index f59809f0bd6034cd7b6e6f3beb977e89fd0c4995..1016cbba29be3fbf6afa3cdd8e159a5517a7d96f 100644 |
--- a/src/js/promise.js |
+++ b/src/js/promise.js |
@@ -40,6 +40,9 @@ |
var resolve = function(value) { |
if (alreadyResolved === true) return; |
alreadyResolved = true; |
+ if (value === promise) { |
+ return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value)); |
+ } |
PromiseResolve(promise, value); |
}; |
@@ -113,11 +116,37 @@ |
} |
} |
+function PromiseCoerce(constructor, x) { |
+ if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { |
+ var then; |
+ try { |
+ then = x.then; |
+ } catch(r) { |
+ return %_Call(PromiseRejected, constructor, r); |
+ } |
+ if (IS_CALLABLE(then)) { |
+ var deferred = NewPromiseCapability(constructor); |
+ try { |
+ %_Call(then, x, deferred.resolve, deferred.reject); |
+ } catch(r) { |
+ deferred.reject(r); |
+ } |
+ return deferred.promise; |
+ } |
+ } |
+ return x; |
+} |
+ |
function PromiseHandle(value, handler, deferred) { |
try { |
%DebugPushPromise(deferred.promise, PromiseHandle); |
var result = handler(value); |
- deferred.resolve(result); |
+ if (result === deferred.promise) |
+ throw MakeTypeError(kPromiseCyclic, result); |
+ else if (IsPromise(result)) |
+ %_Call(PromiseChain, result, deferred.resolve, deferred.reject); |
+ else |
+ deferred.resolve(result); |
} catch (exception) { |
try { deferred.reject(exception); } catch (e) { } |
} finally { |
@@ -164,29 +193,28 @@ |
} |
function PromiseResolve(promise, x) { |
- if (x === promise) { |
- return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x)); |
- } |
- 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); |
+ 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) { |
@@ -217,7 +245,6 @@ |
} else { |
var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; |
result.promise = new C(function(resolve, reject) { |
- // TODO(littledan): Check for resolve and reject being not undefined |
result.resolve = resolve; |
result.reject = reject; |
}); |
@@ -230,13 +257,15 @@ |
} |
function PromiseResolved(x) { |
- return %_Call(PromiseCast, this, x); |
+ if (this === GlobalPromise) { |
+ // Optimized case, avoid extra closure. |
+ return PromiseCreateAndSet(+1, x); |
+ } else { |
+ return new this(function(resolve, reject) { resolve(x) }); |
+ } |
} |
function PromiseRejected(r) { |
- if (!IS_SPEC_OBJECT(this)) { |
- throw MakeTypeError(kCalledOnNonObject, PromiseRejected); |
- } |
var promise; |
if (this === GlobalPromise) { |
// Optimized case, avoid extra closure. |
@@ -250,18 +279,15 @@ |
return promise; |
} |
-// Multi-unwrapped chaining with thenable coercion. |
- |
-function PromiseThen(onResolve, onReject) { |
- var constructor = this.constructor; |
- onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; |
- onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; |
+// Simple chaining. |
+ |
+// PromiseChain a.k.a. flatMap |
+function PromiseChainInternal(constructor, onResolve, onReject) { |
+ onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
+ onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; |
var deferred = NewPromiseCapability(constructor); |
switch (GET_PRIVATE(this, promiseStatusSymbol)) { |
case UNDEFINED: |
- // TODO(littledan): The type check should be called before |
- // constructing NewPromiseCapability; this is observable when |
- // erroneously copying this method to other classes. |
throw MakeTypeError(kNotAPromise, this); |
case 0: // Pending |
GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); |
@@ -291,25 +317,47 @@ |
return deferred.promise; |
} |
-// Chain is left around for now as an alias for then |
function PromiseChain(onResolve, onReject) { |
- return %_Call(PromiseThen, this, onResolve, onReject); |
+ return %_Call(PromiseChainInternal, this, this.constructor, |
+ onResolve, onReject); |
} |
function PromiseCatch(onReject) { |
return this.then(UNDEFINED, onReject); |
} |
+// Multi-unwrapped chaining with thenable coercion. |
+ |
+function PromiseThen(onResolve, onReject) { |
+ onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; |
+ onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; |
+ var that = this; |
+ var constructor = this.constructor; |
+ return %_Call( |
+ PromiseChainInternal, |
+ this, |
+ constructor, |
+ function(x) { |
+ x = PromiseCoerce(constructor, x); |
+ if (x === that) { |
+ return onReject(MakeTypeError(kPromiseCyclic, x)); |
+ } else if (IsPromise(x)) { |
+ return x.then(onResolve, onReject); |
+ } else { |
+ return onResolve(x); |
+ } |
+ }, |
+ onReject |
+ ); |
+} |
+ |
// Combinators. |
function PromiseCast(x) { |
- if (!IS_SPEC_OBJECT(this)) { |
- throw MakeTypeError(kCalledOnNonObject, PromiseCast); |
- } |
if (IsPromise(x) && x.constructor === this) { |
return x; |
} else { |
- return new this(function(resolve, reject) { resolve(x) }); |
+ return new this(function(resolve) { resolve(x) }); |
} |
} |