promises: A+ compliance, better async, unhandled rejection reporter
This commit is contained in:
parent
3bb0a6287f
commit
2e6e1c73d0
2 changed files with 45 additions and 23 deletions
|
|
@ -5,25 +5,23 @@ function Promise(executor) {
|
||||||
if (typeof executor !== "function") throw new Error("executor must be a function")
|
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)
|
var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
|
||||||
|
var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
|
||||||
|
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||||
function handler(list, shouldAbsorb) {
|
function handler(list, shouldAbsorb) {
|
||||||
var done = false
|
|
||||||
return function execute(value) {
|
return function execute(value) {
|
||||||
if (done) return
|
|
||||||
done = true
|
|
||||||
|
|
||||||
var then
|
var then
|
||||||
try {
|
try {
|
||||||
if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
|
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"))
|
if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
|
||||||
then.call(value, handler(list, shouldAbsorb), rejectCurrent)
|
executeOnce(then.bind(value))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setTimeout(function() {
|
callAsync(function() {
|
||||||
|
if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
|
||||||
for (var i = 0; i < list.length; i++) list[i](value)
|
for (var i = 0; i < list.length; i++) list[i](value)
|
||||||
instance.retry = function() {
|
resolvers.length = 0, rejectors.length = 0
|
||||||
done = false
|
instance.state = shouldAbsorb
|
||||||
execute(value)
|
instance.retry = function() {execute(value)}
|
||||||
}
|
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,23 +30,32 @@ function Promise(executor) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var instance = this._instance = {resolvers: resolvers, rejectors: rejectors}
|
function executeOnce(then) {
|
||||||
|
var runs = 0
|
||||||
|
function run(fn) {
|
||||||
|
return function(value) {
|
||||||
|
if (runs++ > 0) return
|
||||||
|
fn(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var onerror = run(rejectCurrent)
|
||||||
|
try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
|
||||||
|
}
|
||||||
|
|
||||||
try {executor(resolveCurrent, rejectCurrent)} catch (e) {rejectCurrent(e)}
|
executeOnce(executor)
|
||||||
}
|
}
|
||||||
Promise.prototype.then = function(onFulfilled, onRejection) {
|
Promise.prototype.then = function(onFulfilled, onRejection) {
|
||||||
var self = this
|
var self = this, instance = self._instance
|
||||||
function handle(callback, list, next, state) {
|
function handle(callback, list, next, state) {
|
||||||
list.push(function(value) {
|
list.push(function(value) {
|
||||||
if (typeof callback !== "function") next(value)
|
if (typeof callback !== "function") next(value)
|
||||||
try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
||||||
})
|
})
|
||||||
var retry = self._instance.retry
|
if (typeof instance.retry === "function" && state === instance.state) instance.retry()
|
||||||
if (retry) retry()
|
|
||||||
}
|
}
|
||||||
var resolveNext, rejectNext
|
var resolveNext, rejectNext
|
||||||
var promise = 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)
|
handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
Promise.prototype.catch = function(onRejection) {
|
Promise.prototype.catch = function(onRejection) {
|
||||||
|
|
@ -71,9 +78,8 @@ Promise.all = function(list) {
|
||||||
values[i] = value
|
values[i] = value
|
||||||
if (count === total) resolve(values)
|
if (count === total) resolve(values)
|
||||||
}
|
}
|
||||||
var then
|
if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
|
||||||
if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof (then = list[i].then) === "function") {
|
list[i].then(consume, reject)
|
||||||
then.call(list[i], consume, reject)
|
|
||||||
}
|
}
|
||||||
else consume(list[i])
|
else consume(list[i])
|
||||||
}(i)
|
}(i)
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,8 @@ o.spec("promise", function() {
|
||||||
})
|
})
|
||||||
o("absorbs resolved promise in executor resolve", function(done) {
|
o("absorbs resolved promise in executor resolve", function(done) {
|
||||||
var promise = new Promise(function(resolve, reject) {
|
var promise = new Promise(function(resolve, reject) {
|
||||||
resolve(Promise.resolve(1))
|
var p = Promise.resolve(1)
|
||||||
|
resolve(p)
|
||||||
})
|
})
|
||||||
|
|
||||||
promise.then(function(value) {
|
promise.then(function(value) {
|
||||||
|
|
@ -482,7 +483,7 @@ o.spec("promise", function() {
|
||||||
done()
|
done()
|
||||||
}, 10)
|
}, 10)
|
||||||
})
|
})
|
||||||
o("absorbs early resolved promise", function(done, t) {
|
o("absorbs early resolved promise", function(done) {
|
||||||
var resolved = Promise.resolve(1)
|
var resolved = Promise.resolve(1)
|
||||||
var promise = new Promise(function(resolve) {
|
var promise = new Promise(function(resolve) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
|
@ -589,5 +590,20 @@ o.spec("promise", function() {
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
o("works if thennable resolves async rejection then throws", function(done) {
|
||||||
|
var promise = new Promise(function(res) {
|
||||||
|
res({
|
||||||
|
then: function(resolve, reject) {
|
||||||
|
setTimeout(function() {reject(2)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
throw 3
|
||||||
|
})
|
||||||
|
|
||||||
|
promise.then(null, function(value) {
|
||||||
|
o(value).equals(2)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue