promise bug fixes (work in progress)
This commit is contained in:
parent
3c9ec0d2d6
commit
3bb0a6287f
2 changed files with 132 additions and 20 deletions
|
|
@ -1,39 +1,55 @@
|
|||
"use strict"
|
||||
|
||||
{
|
||||
function Promise(executor) {
|
||||
if (!(this instanceof Promise)) throw new Error("Promise must be called with `new`")
|
||||
if (typeof executor !== "function") throw new Error("executor must be a function")
|
||||
|
||||
var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
|
||||
function handler(list, shouldAbsorb) {
|
||||
var done = false
|
||||
return function execute(value) {
|
||||
if (shouldAbsorb && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") {
|
||||
if (value === self) rejectCurrent(new Error("Promise cannot be resolved with itself"))
|
||||
value.then(execute, rejectCurrent)
|
||||
if (done) return
|
||||
done = true
|
||||
|
||||
var then
|
||||
try {
|
||||
if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
|
||||
if (value === self) rejectCurrent(new TypeError("Promise can't be resolved w/ itself"))
|
||||
then.call(value, handler(list, shouldAbsorb), rejectCurrent)
|
||||
}
|
||||
else {
|
||||
setTimeout(function() {
|
||||
for (var i = 0; i < list.length; i++) list[i](value)
|
||||
instance.retry = function() {
|
||||
done = false
|
||||
execute(value)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
else {
|
||||
setTimeout(function() {
|
||||
for (var i = 0; i < list.length; i++) list[i](value)
|
||||
resolvers.length = 0, rejectors.length = 0
|
||||
}, 0)
|
||||
catch (e) {
|
||||
rejectCurrent(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
var instance = this._instance = {resolvers: resolvers, rejectors: rejectors}
|
||||
|
||||
this._instance = {resolvers: resolvers, rejectors: rejectors}
|
||||
try {executor(resolveCurrent, rejectCurrent)} catch (e) {rejectCurrent(e)}
|
||||
}
|
||||
Promise.prototype.then = function(onFulfilled, onRejection) {
|
||||
function handle(callback, list) {
|
||||
if (typeof callback === "function") {
|
||||
list.push(function(value) {
|
||||
try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
||||
})
|
||||
}
|
||||
var self = this
|
||||
function handle(callback, list, next, state) {
|
||||
list.push(function(value) {
|
||||
if (typeof callback !== "function") next(value)
|
||||
try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
||||
})
|
||||
var retry = self._instance.retry
|
||||
if (retry) retry()
|
||||
}
|
||||
var resolveNext, rejectNext
|
||||
handle(onFulfilled, this._instance.resolvers), handle(onRejection, this._instance.rejectors)
|
||||
return new Promise(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
|
||||
var promise = new Promise(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
|
||||
handle(onFulfilled, this._instance.resolvers, resolveNext, true), handle(onRejection, this._instance.rejectors, rejectNext, false)
|
||||
return promise
|
||||
}
|
||||
Promise.prototype.catch = function(onRejection) {
|
||||
return this.then(null, onRejection)
|
||||
|
|
@ -55,8 +71,9 @@ Promise.all = function(list) {
|
|||
values[i] = value
|
||||
if (count === total) resolve(values)
|
||||
}
|
||||
if ((typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
|
||||
list[i].then(consume, reject)
|
||||
var then
|
||||
if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof (then = list[i].then) === "function") {
|
||||
then.call(list[i], consume, reject)
|
||||
}
|
||||
else consume(list[i])
|
||||
}(i)
|
||||
|
|
@ -72,3 +89,4 @@ Promise.race = function(list) {
|
|||
}
|
||||
|
||||
module.exports = Promise
|
||||
}
|
||||
|
|
@ -133,6 +133,22 @@ o.spec("promise", function() {
|
|||
|
||||
promise.then(null, done)
|
||||
})
|
||||
o("non-function onFulfilled is ignored", function(done) {
|
||||
var promise = Promise.resolve(1)
|
||||
|
||||
promise.then(null, null).then(function(value) {
|
||||
o(value).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("non-function onFulfilled is ignored", function(done) {
|
||||
var promise = Promise.resolve(1)
|
||||
|
||||
promise.then(null).then(function(value) {
|
||||
o(value).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("reject", function() {
|
||||
o("rejects once", function(done) {
|
||||
|
|
@ -247,6 +263,14 @@ o.spec("promise", function() {
|
|||
done()
|
||||
})
|
||||
})
|
||||
o("non-function onRejected is ignored", function(done) {
|
||||
var promise = Promise.reject(1)
|
||||
|
||||
promise.then(function() {}, null).then(null, function(value) {
|
||||
o(value).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("promise absorption", function() {
|
||||
o("absorbs resolved promise via static resolver", function(done) {
|
||||
|
|
@ -440,6 +464,37 @@ o.spec("promise", function() {
|
|||
done()
|
||||
})
|
||||
})
|
||||
o("promise stays pending if absorbed promise is pending", function(done) {
|
||||
var promise = new Promise(function(resolve) {resolve()})
|
||||
var fulfilled = false, rejected = false
|
||||
|
||||
promise.then(function() {
|
||||
return new Promise(function() {})
|
||||
}).then(function() {
|
||||
fulfilled = true
|
||||
}, function() {
|
||||
rejected = false
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
o(fulfilled).equals(false)
|
||||
o(rejected).equals(false)
|
||||
done()
|
||||
}, 10)
|
||||
})
|
||||
o("absorbs early resolved promise", function(done, t) {
|
||||
var resolved = Promise.resolve(1)
|
||||
var promise = new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
resolve(resolved)
|
||||
}, 10)
|
||||
})
|
||||
|
||||
promise.then(function(value) {
|
||||
o(value).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("race", function() {
|
||||
o("resolves to first resolved", function(done) {
|
||||
|
|
@ -496,4 +551,43 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
})
|
||||
o.spec("A+ compliance", function() {
|
||||
o("accesses then only once", function(done) {
|
||||
var readCount = 0
|
||||
var promise = Promise.resolve(1).then(function() {
|
||||
return Object.create(null, {
|
||||
then: {
|
||||
get: function () {
|
||||
++readCount
|
||||
return function(onFulfilled) {
|
||||
onFulfilled()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
promise.then(function(value) {
|
||||
o(readCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("works if thennable resolves twice", function(done) {
|
||||
var promise = Promise.resolve({
|
||||
then: function(res) {
|
||||
res({
|
||||
then: function(resolve) {
|
||||
setTimeout(function() {resolve(2)})
|
||||
}
|
||||
})
|
||||
res(1)
|
||||
}
|
||||
})
|
||||
|
||||
promise.then(function(value) {
|
||||
o(value).equals(2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue