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:
parent
6928465bd9
commit
2f57dff3ea
118
src/promise.js
118
src/promise.js
@ -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;
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user