Promise assimilation fix.

Let x be a fulfilled promise and y be another promise. |x.then(() => y)|
should call |y.then|, but the current implementation calls PromiseChain.
We can see the difference when we set a custom function to |y.then|.

This CL fixes the spec violation, but as a result |then| is no longer
a wrapper of |chain| and in some cases it does not work well with
|accept| or |chain|. That is not a problem for ES6 promise users because
ES6 promise doesn't have them.

LOG=N
BUG=477921

Review URL: https://codereview.chromium.org/1098663002

Cr-Commit-Position: refs/heads/master@{#28926}
This commit is contained in:
yhirano 2015-06-11 00:42:37 -07:00 committed by Commit bot
parent 6928465bd9
commit 2f57dff3ea
2 changed files with 231 additions and 40 deletions

View File

@ -105,17 +105,25 @@ function PromiseCoerce(constructor, x) {
return x;
}
function PromiseHandle(value, handler, deferred) {
function PromiseHandle(value, handler, deferred, thenable) {
try {
%DebugPushPromise(deferred.promise, PromiseHandle);
DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler);
var result = handler(value);
if (result === deferred.promise)
if (result === deferred.promise) {
throw MakeTypeError(kPromiseCyclic, result);
else if (IsPromise(result))
} else if (IsPromise(result) && thenable) {
var then = result.then;
if (IS_SPEC_FUNCTION(then)) {
PromiseCallThenInSeparateTask(result, deferred, then);
} else {
deferred.resolve(result);
}
} else if (IsPromise(result)) {
%_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
else
} else {
deferred.resolve(result);
}
} catch (exception) {
try { deferred.reject(exception); } catch (e) { }
} finally {
@ -123,14 +131,38 @@ function PromiseHandle(value, handler, deferred) {
}
}
function PromiseCallThenInSeparateTask(result, deferred, then) {
var id, name, instrumenting = DEBUG_IS_ACTIVE;
%EnqueueMicrotask(function() {
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "willCall", id: id, name: name });
}
try {
%_CallFunction(result, deferred.resolve, deferred.reject, then);
} catch(exception) {
try { deferred.reject(exception); } catch (e) { }
}
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "didCall", id: id, name: name });
}
});
if (instrumenting) {
id = ++lastMicrotaskId;
name = "Promise.prototype.then";
%DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
}
}
function PromiseEnqueue(value, tasks, status) {
var id, name, instrumenting = DEBUG_IS_ACTIVE;
%EnqueueMicrotask(function() {
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
}
for (var i = 0; i < tasks.length; i += 2) {
PromiseHandle(value, tasks[i], tasks[i + 1])
for (var i = 0; i < tasks.length; i += 3) {
// tasks[i: i + 2] consists of the reaction handler, the associated
// deferred object and whether the thenable handling is required.
PromiseHandle(value, tasks[i], tasks[i + 1], tasks[i + 2])
}
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
@ -143,6 +175,41 @@ function PromiseEnqueue(value, tasks, status) {
}
}
function PromiseChainInternal(onResolve, onReject, thenable) {
onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
var deferred = %_CallFunction(this.constructor, PromiseDeferred);
switch (GET_PRIVATE(this, promiseStatus)) {
case UNDEFINED:
throw MakeTypeError(kNotAPromise, this);
case 0: // Pending
GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred, thenable);
GET_PRIVATE(this, promiseOnReject).push(onReject, deferred, thenable);
break;
case +1: // Resolved
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onResolve, deferred, thenable],
+1);
break;
case -1: // Rejected
if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) {
// Promise has already been rejected, but had no handler.
// Revoke previously triggered reject event.
%PromiseRevokeReject(this);
}
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onReject, deferred, thenable],
-1);
break;
}
// Mark this promise as having handler.
SET_PRIVATE(this, promiseHasHandler, true);
if (DEBUG_IS_ACTIVE) {
%DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
}
return deferred.promise;
}
function PromiseIdResolveHandler(x) { return x }
function PromiseIdRejectHandler(r) { throw r }
@ -224,38 +291,8 @@ function PromiseRejected(r) {
// Simple chaining.
function PromiseChain(onResolve, onReject) { // a.k.a. flatMap
onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
var deferred = %_CallFunction(this.constructor, PromiseDeferred);
switch (GET_PRIVATE(this, promiseStatus)) {
case UNDEFINED:
throw MakeTypeError(kNotAPromise, this);
case 0: // Pending
GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
break;
case +1: // Resolved
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onResolve, deferred],
+1);
break;
case -1: // Rejected
if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) {
// Promise has already been rejected, but had no handler.
// Revoke previously triggered reject event.
%PromiseRevokeReject(this);
}
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onReject, deferred],
-1);
break;
}
// Mark this promise as having handler.
SET_PRIVATE(this, promiseHasHandler, true);
if (DEBUG_IS_ACTIVE) {
%DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
}
return deferred.promise;
return %_CallFunction(this, onResolve, onReject, false,
PromiseChainInternal);
}
function PromiseCatch(onReject) {
@ -286,7 +323,8 @@ function PromiseThen(onResolve, onReject) {
}
},
onReject,
PromiseChain
true,
PromiseChainInternal
);
}
@ -348,7 +386,7 @@ function PromiseRace(iterable) {
function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
var queue = GET_PRIVATE(promise, promiseOnReject);
if (IS_UNDEFINED(queue)) return false;
for (var i = 0; i < queue.length; i += 2) {
for (var i = 0; i < queue.length; i += 3) {
if (queue[i] != PromiseIdRejectHandler) return true;
if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) {
return true;

View File

@ -390,6 +390,59 @@ function assertAsyncDone(iteration) {
assertAsyncRan()
})();
(function() {
var p1 = Promise.accept(5)
var p2 = Promise.accept(p1)
var called = false
p2.then = function(onResolve, onReject) {
called = true;
return call(Promise.prototype.then, p2, onResolve, onReject)
}
var p3 = Promise.accept(p2)
p3.chain(
function(x) {
assertAsync(x === p2 && !called, "resolved/thenable-promise/chain")
},
assertUnreachable)
assertAsyncRan()
})();
(function() {
var p1 = Promise.accept(5)
var p2 = Promise.accept(p1)
var called = false
p2.then = function(onResolve, onReject) {
called = true
return call(Promise.prototype.then, p2, onResolve, onReject)
}
var p3 = Promise.accept(p2)
p3.then(
function(x) {
assertAsync(x === 5 && called, "resolved/thenable-promise/then")
},
assertUnreachable)
assertAsyncRan()
})();
(function() {
var p1 = Promise.accept(5)
var called = false
var p3 = p1.then(function(x) {
var p2 = Promise.accept(5)
p2.then = function(onResolve, onReject) {
called = true
throw 25
}
return p2
});
p3.then(
assertUnreachable,
function(x) {
assertAsync(called && x === 25, "thenable-promise/then-call-throw")
})
assertAsyncRan()
})();
(function() {
var deferred = Promise.defer()
var p1 = deferred.promise
@ -518,6 +571,106 @@ function assertAsyncDone(iteration) {
assertAsyncRan()
})();
(function() {
var deferred = Promise.defer()
var deferred2 = Promise.defer()
var deferred3 = Promise.defer()
var p1 = deferred.promise
var p2 = deferred2.promise
var p3 = deferred3.promise
var called = false
p2.then = function(onResolve, onReject) {
called = true
return call(Promise.prototype.then, p2, onResolve, onReject)
}
p3.chain(
function(x) { assertAsync(x === p2 && !called,
"chain/resolve/thenable-promise") },
assertUnreachable
)
deferred3.resolve(p2)
deferred2.resolve(p1)
deferred.resolve(5)
assertAsyncRan()
})();
(function() {
var deferred = Promise.defer()
var deferred2 = Promise.defer()
var deferred3 = Promise.defer()
var p1 = deferred.promise
var p2 = deferred2.promise
var p3 = deferred3.promise
var called = false
p2.then = function(onResolve, onReject) {
called = true
return call(Promise.prototype.then, p2, onResolve, onReject)
}
p3.then(
function(x) { assertAsync(x === 5 && called,
"then/resolve/thenable-promise") },
assertUnreachable
)
deferred3.resolve(p2)
deferred2.resolve(p1)
deferred.resolve(5)
assertAsyncRan()
})();
(function() {
var deferred = Promise.defer()
var deferred2 = Promise.defer()
var deferred3 = Promise.defer()
var p1 = deferred.promise
var p2 = deferred2.promise
var p3 = deferred3.promise
var called = false
p2.then = function(onResolve, onReject) {
called = true
return call(Promise.prototype.then, p2, onResolve, onReject)
}
p3.chain(
function(x) { assertAsync(x === p2 && !called,
"chain/reject/thenable-promise") },
assertUnreachable
)
deferred3.resolve(p2)
deferred2.resolve(p1)
deferred.reject(5)
assertAsyncRan()
})();
(function() {
var deferred = Promise.defer()
var deferred2 = Promise.defer()
var deferred3 = Promise.defer()
var p1 = deferred.promise
var p2 = deferred2.promise
var p3 = deferred3.promise
var called = false
p2.then = function(onResolve, onReject) {
called = true
return call(Promise.prototype.then, p2, onResolve, onReject)
}
p3.then(
assertUnreachable,
function(x) { assertAsync(x === 5 && called,
"then/reject/thenable-promise") }
)
deferred3.resolve(p2)
deferred2.resolve(p1)
deferred.reject(5)
assertAsyncRan()
})();
(function() {
var p1 = Promise.accept(5)
var p2 = Promise.accept(p1)