1 什么是 Promise
Promise 是前端流行的异步编程解决方案,而Promise/A+ 是一组关于 Promise 实现的开放标准。
2 如何使用 Promise
构建 Promise 对象,传入异步执行过程,描述何时解决(resolve)、何时拒绝(reject)
var p1 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function() {
if ( /* 执行成功时 */ ) {
resolve(value)
} else if ( /* 执行失败时 */ ) {
reject(reason)
}
}, 2000)
})
在 Promise 上调用 then 方法,传入接收 解决结果/拒绝原因 的回调函数
p1.then(
function(value) {
/* 使用“解决结果” */
},
function(reason) {
/* 使用“拒绝原因” */
}
)
then 可被多次调用,且多次调用返回的结果一致
p1.then(onFulfilled1, onRejected1)
p1.then(onFulfilled2, onRejected2)
p1.then(onFulfilled3, onRejected3)
由于 then 每次调用都会返回一个新的 promise 对象,所以 then 可被链式调用
p1
.then(onFulfilled1, onRejected1)
.then(onFulfilled2, onRejected2)
.then(onFulfilled3, onRejected3)
3 Promise/A+ 规范与实现
3.1 Promise 的三种状态
- pending(等待中)
- fulfilled(已完成)
- rejected(已拒绝)
新生成的 Promise 处于 pending 状态,依据用户的代码逻辑,可能变成 fulfilled 或 rejected。
Promise 一旦处于 fulfilled 或 rejected 状态,就无法再被改变。
代码实现如下:
var PENDING = 'pending'
var FULFILLED = 'fulfilled'
var REJECTED = 'rejected'
function Promise(fn) {
var that = this
that.status = PENDING
that.value = void 0 // 保存 resolve 调用时传入的 value
that.reason = void 0 // 保存 reject 调用时传入的 reason
var resolve = function(value) {
if (that.status === PENDING) {
that.status = FULFILLED
that.value = value
}
}
var reject = function(reason) {
if (that.status === PENDING) {
that.status = REJECTED
that.reason = reason
}
}
fn(resolve, reject)
}
3.2 promise.then 详解
then 方法包含两个可选参数
promise.then(onFulfilled, onRejected)
then 方法调用后必须返回一个新的 promise
var newPromise = promise.then(onFulfilled, onRejected)
onFulfilled / onRejected 的类型如果不是 function,忽略该参数
onFulfilled 必须在 promise 的状态变成 fulfilled 后被调用,并将 value 作为第一个参数传入
onRejected 必须在 promise 的状态变成 rejected 后被调用,并将 reason 作为第一个参数传入
-
onFulfilled / onRejected 必须是异步执行的
Promise.prototype.then = function(onFulfilled, onRejected) { onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : function() { } onRejected = (typeof onRejected === 'function') ? onRejected : function() { } var that = this var newPromise = null if (that.status === FULFILLED) { newPromise = new Promise(function(resolve, reject) { // 确保异步调用 setTimeout(function() { try { var x = onFulfilled(that.value) } catch (e) { reject(e) } }) }) } if (that.status === REJECTED) { newPromise = new Promise(function(resolve, reject) { // 确保异步调用 setTimeout(function() { try { var x = onRejected(that.reason) } catch (e) { reject(e) } }) }) } if (that.status === PENDING) { newPromise = new Promise(function(resolve, reject) { // 等待 status 变成 FULFILLED/REJECTED 才能调用 }) } return newPromise }
通常 then 方法调用时,promise 仍处于 pending 状态,那么必须推迟 onFulfilled/onRejected 的调用时机,使得 promise 的状态变成 fulfilled/rejected 后 onFulfilled/onRejected 才能被调用。
并且,如果 then 方法被调用多次,onFulfilled/onRejected 的调用顺序必须与 then 方法被调用的顺序一致。
Promise 的改动:
// 为 promise 保存 onFulfilled/onRejected 回调函数的队列
that.fulfilledCallbacks = []
that.rejectedCallbacks = []
var resolve = function(value) {
if (that.status === PENDING) {
that.status = FULFILLED
that.value = value
// pending 时加入的 onFulfilled 队列按顺序调用
that.fulfilledCallbacks.length > 0 && that.fulfilledCallbacks.forEach(function(callback) {
callback()
})
}
}
var reject = function(reason) {
if (that.status === PENDING) {
that.status = REJECTED
that.reason = reason
// pending 时加入的 onRejected 队列按顺序调用
that.rejectedCallbacks.length > 0 && that.rejectedCallbacks.forEach(function(callback) {
callback()
})
}
}
Promise.prototype.then 的改动:
if (that.status === PENDING) {
newPromise = new Promise(function(resolve, reject) {
// 等待 status 变成 FULFILLED/REJECTED 才能调用
that.fulfilledCallbacks.push(function() {
setTimeout(function() {
try {
var x = onFulfilled(that.value)
} catch (e) {
reject(e)
}
})
})
that.rejectedCallbacks.push(function() {
setTimeout(function() {
try {
var x = onRejected(that.reason)
} catch (e) {
reject(e)
}
})
})
})
}
如果 onFulfilled/onRejected 的类型不是 function,那么将对应的 value/reason 传递下去。
Promise.prototype.then 的改动:
onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : function(value) { return value }
onRejected = (typeof onRejected === 'function') ? onRejected : function(reason) { throw reason }
3.3 [[Resolve]](promise, x) 详解
由于 Promise 是先有社区实现,再逐渐形成规范,许多早期实现的 Promise 库与规范并不完全一致。
例如:jQuery.Deferred()
为了兼容这些并不完全符合规范的实现,[[Resolve]](promise, x) 有如下要求:
- promise 与 x 不能是相等的值或同一个对象
- x 必须是一个 thenable 对象
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('promise === x!!'))
}
// 依据规范明细 2.3.3
var called = false
if (!!x && (typeof x === 'function' || typeof x === 'object')) {
try {
var then = x.then
if (typeof then === 'function') {
then.call(x, function(y) {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
}, function(r) {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then 的改动:
if (that.status === FULFILLED) {
newPromise = new Promise(function(resolve, reject) {
// 确保异步调用
setTimeout(function() {
try {
var x = onFulfilled(that.value)
// [[Resolve]](promise, x)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (that.status === REJECTED) {
newPromise = new Promise(function(resolve, reject) {
// 确保异步调用
setTimeout(function() {
try {
var x = onRejected(that.reason)
// [[Resolve]](promise, x)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (that.status === PENDING) {
newPromise = new Promise(function(resolve, reject) {
// 等待 status 变成 FULFILLED/REJECTED 才能调用
that.fulfilledCallbacks.push(function() {
setTimeout(function() {
try {
var x = onFulfilled(that.value)
// [[Resolve]](promise, x)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
that.rejectedCallbacks.push(function() {
setTimeout(function() {
try {
var x = onRejected(that.reason)
// [[Resolve]](promise, x)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
})
}
这样,我们就实现了一个符合 Promise/A+ 规范的 Promise 库。
4 测试
那么,怎么才能验证我们实现的 Promise 库确实是完全符合规范的呢?
规范提供了测试套件 test suit,依据该测试套件的要求,我们还需要进行一点小小的修改。
// 封装下述 API 用于测试
Promise.deferred = Promise.defer = function() {
var dfd = {}
dfd.promise = new Promise(function(fulfill, reject) {
dfd.resolve = fulfill
dfd.reject = reject
})
return dfd
}
Promise.resolve = function(value) {
var promise = new Promise(function(fulfill, reject) {
resolvePromise(promise, value, fulfill, reject)
})
return promise
}
Promise.reject = function(reason) {
return new Promise(function(fulfill, reject) {
reject(reason)
})
}
module.exports = Promise
然后,使用 npm 安装测试库 npm install --save-dev promises-aplus-tests
,编写简单的测试代码并运行
var promisesAplusTests = require("promises-aplus-tests")
var adapter = requirel("./MyPromise.js")
promisesAplusTests(adapter, function (err) {
// All done; output is in the console. Or check `err` for number of failures.
});
最后,附上代码全文:
var PENDING = 'pending'
var FULFILLED = 'fulfilled'
var REJECTED = 'rejected'
function Promise(fn) {
var that = this
that.status = PENDING
that.value = void 0 // 保存 resolve 调用时传入的 value
that.reason = void 0 // 保存 reject 调用时传入的 reason
// 为 promise 保存 onFulfilled/onRejected 回调函数的队列
that.fulfilledCallbacks = []
that.rejectedCallbacks = []
var resolve = function(value) {
if (that.status === PENDING) {
that.status = FULFILLED
that.value = value
// pending 时加入的 onFulfilled 队列按顺序调用
that.fulfilledCallbacks.length > 0 && that.fulfilledCallbacks.forEach(function(callback) {
callback()
})
}
}
var reject = function(reason) {
if (that.status === PENDING) {
that.status = REJECTED
that.reason = reason
// pending 时加入的 onRejected 队列按顺序调用
that.rejectedCallbacks.length > 0 && that.rejectedCallbacks.forEach(function(callback) {
callback()
})
}
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('promise === x!!'))
}
// 依据规范明细 2.3.3
var called = false
if (!!x && (typeof x === 'function' || typeof x === 'object')) {
try {
var then = x.then
if (typeof then === 'function') {
then.call(x, function(y) {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
}, function(r) {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : function(value) { return value }
onRejected = (typeof onRejected === 'function') ? onRejected : function(reason) { throw reason }
var that = this
var newPromise = null
if (that.status === FULFILLED) {
newPromise = new Promise(function(resolve, reject) {
// 确保异步调用
setTimeout(function() {
try {
var x = onFulfilled(that.value)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (that.status === REJECTED) {
newPromise = new Promise(function(resolve, reject) {
// 确保异步调用
setTimeout(function() {
try {
var x = onRejected(that.reason)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (that.status === PENDING) {
newPromise = new Promise(function(resolve, reject) {
// 等待 status 变成 FULFILLED/REJECTED 才能调用
that.fulfilledCallbacks.push(function() {
// 确保异步调用
setTimeout(function() {
try {
var x = onFulfilled(that.value)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
that.rejectedCallbacks.push(function() {
// 确保异步调用
setTimeout(function() {
try {
var x = onRejected(that.reason)
resolvePromise(newPromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
})
}
return newPromise
}
Promise.deferred = Promise.defer = function() {
var dfd = {}
dfd.promise = new Promise(function(fulfill, reject) {
dfd.resolve = fulfill
dfd.reject = reject
})
return dfd
}
Promise.resolve = function(value) {
var promise = new Promise(function(fulfill, reject) {
resolvePromise(promise, value, fulfill, reject)
})
return promise
}
Promise.reject = function(reason) {
return new Promise(function(fulfill, reject) {
reject(reason)
})
}
module.exports = Promise