Promise 现在是 JS 的常用特性了. 我在阅读你不知道的 JavaScript 这本书时尝试根据书中提供的特性实现了一个 Promise 的模拟. 写的过程中没有参照 Q, bluebird 等开源库, 所以和他们的实现, 以及语言原生的实现可能完全不同, 仅供抛砖引玉之用. 这篇文章恰当的标题或许是: 如果让你来实现 Promise 你会怎么做.
你可以去我的 playground wzlib 找到源代码和单元测试.
function Prom(immediate, res_handler, err_handler) {
p = this
p.subs = []
p.status = 0 // 0 pending, 1 resolved, 2 rejected
p.res_handler = res_handler
p.err_handler = err_handler
p.val = null
p.err = null
// created two closures, they would be passed to cb as resolve and reject
p.res = function(val) {
if (p.status === 0) {
p.status = 1
p.val = val
setTimeout(() => {
const next_val = res_handler && res_handler.call(null, val)
p.subs.forEach(sub_p => {
sub_p.notify(1, next_val || val)
})
})
}
}
p.rej = function(err) {
if (p.status === 0) {
p.status = 2
p.err = err
setTimeout(() => {
err_handler && err_handler.call(null, err)
p.subs.forEach(sub_p => {
sub_p.notify(2, err)
})
})
}
}
if (typeof immediate === 'function') immediate.call(null, p.res, p.rej)
}
Prom.prototype.then = function(res_handler, err_handler) {
p = this
var sub_p = new Prom(null, res_handler, err_handler)
p.subs.push(sub_p)
if (p.status !== 0) {
p.status === 1 ? sub_p.notify(1, p.val) : sub_p.notify(2, p.err)
}
return sub_p
}
Prom.prototype.catch = function(err_handler) {
p = this
var sub_p = new Prom(null, null, err_handler)
p.subs.push(sub_p)
if (p.status === 2) sub_p.notify(2, p.err)
return sub_p
}
Prom.prototype.notify = function(status, playload) {
p = this
status === 1 ? p.res(playload) : p.rej(playload)
}
Prom.resolve = function(val) {
if (val instanceof Prom) return val
const p = new Prom()
p.notify(1, val)
return p
}
Prom.reject = function(err) {
if (val instanceof Prom) return val
const p = new Prom()
p.notify(2, err)
return p
}
Prom.all = function all(jobs) {
if (!Array.isArray(jobs) || jobs.length === 0) return Prom.reject()
const len = jobs.length
const values = new Array(len)
let counter = 0
return new Prom((resolve, reject) => {
for (let i = 0; i < len; i++) {
Prom.resolve(jobs[i])
.then(value => {
values[i] = value
if (++counter === len) resolve(values)
})
.catch(err => reject(err))
}
})
}
Prom.race = function race(jobs) {
if (!Array.isArray(jobs) || jobs.length === 0) return Prom.reject()
return new Prom((resolve, reject) => {
jobs.forEach(job => {
Prom.resolve(job)
.then(value => resolve(value))
.catch(err => reject(err))
})
})
}
Prom.none = function(jobs) {
if (!Array.isArray(jobs) || jobs.length === 0) return Prom.resolve()
const len = jobs.length
let counter = 0
return new Prom((resolve, reject) => {
for (let i = 0; i < len; i++) {
Prom.resolve(jobs[i])
.then(value => reject({ i, value }))
.catch(err => {
if (++counter === len) resolve()
})
}
})
}
Prom.any = function any(jobs) {
if (!Array.isArray(jobs) || jobs.length === 0) return Prom.resolve()
return new Prom((res, rej) => {
jobs.forEach(job => {
Prom.resolve(job)
.then(value => res(value))
.catch(err => {
if (++counter === len) reject()
})
})
})
}
Prom.last = function last(jobs) {
if (!Array.isArray(jobs) || jobs.length === 0) return Prom.resolve()
const len = jobs.length
let counter = 0
return new Prom((res, rej) => {
jobs.forEach(job => {
Prom.resolve(job)
.then(value => {
if (++counter === len) res(value)
})
.catch(err => rej(err))
})
})
}
module.exports = Prom