前言
在目前的前端开发环境中,Promise的使用越来越广泛。今天我就来和大家一起从零开始手写一个符合PromiseA+规范的Promise类,让大家在熟悉Promise使用的同时,能够了解它的实现原理。
为什么会有Promise?
在Promise没有出现之前,我们在解决异步问题的时候,使用的最多的就是回调函数。比如$.ajax:
$.ajax({
...
success: function(res){
// success callback
}
})
假设一种场景,一个Http请求要在另一个的基础上发出,我们就得这样写:
$.ajax({
...
success: function(res){
$.ajax({
...
success: function(res){
// success callback
}
})
}
})
如果有更多层的嵌套的话,我们的代码就会写成一个死亡嵌套:
$.ajax({
success: function(res){
$.ajax({
success: function(res){
$.ajax({
success: function(res){
...
}
})
}
})
}
})
这样的代码首先在写法上就很不优雅,让人头晕目眩。在这种情况下,Promise应运而生。它就是异步编程的一种解决方案,支持链式编程,我们再也不需要多层回调嵌套来实现异步代码的编写。
Promise的基本特性
很多库都有自己对Promise的实现,并且在实现原理上会有差距,那为了兼容它们,就有了PromiseA+规范. 所有的Promise实现都要符合这个规范。
特性
new Promise((resolve, reject) => {
resolve('this is the value')
reject('this is the reason')
})
- Promise必须是一个带有then方法的对象或者函数。
- Promise的状态必须是 pending/fulfilled/rejected 之一。
- 如果是pending状态,则可以变为fulfilled(成功)或者rejected(失败)。
- 如果是fulfilled状态。
- 不能再变为其它任何状态。
- 必须有一个value(executor里面resolve的值,
resolve(value)),并且这个value是不可变的。
- 如果是rejected状态。
- 不能再变为其它任何状态。
- 必须有一个reason(代码执行过程中捕获到的错误,
reject(err)),并且这个reason是不可变的。
- Promise支持链式编程,也就是说then方法返回的也会是一个Promise实例。
实现自己的Promise
帮助方法
const isArray = validateType('Array')
const isObject = validateType('Object')
const isFunction = validateType('Function')
const PROMISE_STATUS = Object.freeze({
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED'
})
function validateType(type) {
return function (source) {
return Object.prototype.toString.call(source) === `[object ${type}]`
}
}
function isPromise(source) {
return source && isObject(source) && isFunction(source.then)
}
构造函数
先来定义我们需要的几个属性和方法
function Promise(executor){
this.value = undefined // 存储成功的值
this.reason = undefined // 存储失败时抛出的异常信息
this.status = PROMISE_STATUS.PENDING // Promise的状态
// 分别存储成功和失败的回调函数,因为then是可以被多次调用的,也就是说可能会有多个回调函数,所以这里用数组来存储
this.fulfilledCallbacks = []
this.rejectedCallbacks = []
// ------------------------------------------------------------------------------------------------------
function resolve(value) {
// executor函数的第一个参数,用来执行成功后传递value。
}
function reject(reason) {
// executor函数的第二个参数,用来在执行过程中发生异常时捕获异常。
}
}
接下来我们需要在构造器里面执行我们传递进来的执行函数,如果在执行过程中有异常抛出,直接使用reject捕获。
function Promise(executor){
this.value = undefined // 存储成功的值
this.reason = undefined // 存储失败时抛出的异常信息
this.status = PROMISE_STATUS.PENDING // Promise的状态
// 分别存储成功和失败的回调函数,因为then是可以被多次调用的,也就是说可能会有多个回调函数,所以这里用数组来存储
this.fulfilledCallbacks = []
this.rejectedCallbacks = []
// 执行传递进来的executor函数,如果在执行过程中有异常抛出,直接使用reject捕获。
try {
executor(resolve.bind(this), reject.bind(this))
} catch (err) {
reject(err)
}
// ------------------------------------------------------------------------------------------------------
function resolve(value) {
// executor函数的第一个参数,用来执行成功后传递value。
}
function reject(reason) {
// executor函数的第二个参数,用来在执行过程中发生异常时捕获异常。
}
}
实例方法
promise.then()
const p = new Promise((resolve, reject) => {
// Using setTimeout to simulate async code.
setTimeout(() => resolve('success'), 1000)
})
p.then(
value => {
// onFulfilled callback
console.log(value) // success
},
reason => {
// onRejected callback
}
)
- then方法接收两个参数,onFulfilled和onRejected 这两个参数是可选的但必须是函数,否则会被忽略。(PromiseA+ 2.2.1)
- 当Promise的状态变成fulfilled的时候,onFulfilled会被执行,同理,变成rejected的时候,onRejected会被执行。(PromiseA+ 2.2.2/2.2.3)
- 当执行then方法时Promise的状态已经不是pending了,onFulfilled和onRejected会被立即执行 (PromiseA+ 2.2.2/2.2.3)
- Promise的then方法可以多次被调用(像我上面的案列代码)。 (PromiseA+2.2.6)
Promise.prototype.then = function(onFulfilled, onRejected) {
const { status, value, reason } = this
switch(status){
case PROMISE_STATUS.FULFILLED:
onFulfilled(value)
break
case PROMISE_STATUS.FULFILLED:
onRejected(value)
break
case PROMISE_STATUS.PENDING:
this.rejectedCallbacks.push(onRejected)
this.fulfilledCallbacks.push(onFulfilled)
}
}
如果是fulfilled或者rejected状态,直接执行回调函数,如果是pending状态,将回调函数入栈,等待状态改变之后再依次执行。
那到底状态是什么时候改变的,我们怎么知道的呢? 来看一段代码
const p = new Promise((resolve, reject) => {
// Using setTimeout to simulate async code.
setTimeout(() => resolve('success'), 1000)
})
在executor里面我们可以得到两个参数,一个resolve,另一个reject。 如果执行成功了就调用resolve,失败了就调用reject。所以只要用户调用了resolve或者reject,那就表明Promise的状态发生了改变。所以我们回去实现一下构造器里面的resolve和reject方法。
function resolve(value) {
// 如果resolve的是一个promise,我们就递归执行直到resolve的不是一个promise
if (value instanceof Promise) return value.then(resolve.bind(this), reject.bind(this))
// 因为Promise的状态是不可逆的,所以一旦状态变成了fulfilled或者rejected,就不会再有任何变化。
if (this.status !== PROMISE_STATUS.PENDING) return
this.value = value
this.status = PROMISE_STATUS.FULFILLED
// 此时状态已经更新为fulfilled,循环执行所有的回调。
this.fulfilledCallbacks.forEach(fulfilledCallback => fulfilledCallback(value))
}
function reject(reason) {
if (this.status !== PROMISE_STATUS.PENDING) return
this.reason = reason
this.status = PROMISE_STATUS.REJECTED
this.rejectedCallbacks.forEach(rejectedCallback => rejectedCallback(reason))
}
到这里一个简单的Promise已经实现了,不过还有一个问题,就是上面提到的Promise是支持链式调用的:
这就意味着then方法返回的应该也是一个Promise (PromiseA+ 3.3):
Promise.prototype.then = function(onFulfilled, onRejected) {
const { status, value, reason } = this
let promise2 = new Promise((resolve, reject) => {
switch(status){
case PROMISE_STATUS.FULFILLED:
onFulfilled(value)
break
case PROMISE_STATUS.FULFILLED:
onRejected(value)
break
case PROMISE_STATUS.PENDING:
this.rejectedCallbacks.push(onRejected)
this.fulfilledCallbacks.push(onFulfilled)
}
})
return promise2
}
promise2 = promise1.then(onFulfilled, onRejected)
那么promise2的状态应该怎么改变呢?
- 如果onFulfilled或者onRejected返回一个x,那么需要执行
[[Resolve]](promise2, x)。 (PromiseA+ 2.2.7.1) - 如果onFulfilled或者onRejected抛出一个异常e,promise2的状态变为rejected,并且以e作为异常的原因。 (PromiseA+ 2.2.7.2)
- 如果onFulfilled不是一个函数并且promise1已经fulfilled,那么promise2也要变为fulfilled,并且继承promise1的value。 (PromiseA+ 2.2.7.3)
- 如果onRejected不是一个函数并且promise1已经rejected,那么promise2也要变为rejected,并且继承promise1的reason。 (PromiseA+ 2.2.7.3)
这里我们需要一个帮助方法来帮助我们处理promise2的状态。
function resolvePromise(promise2, x, resolve, reject) {
if (x && (isObject(x) || isFunction(x))) {
try {
let then = x.then
if (isFunction(then)) {
then.call(
x,
y => {
resolvePromise(promise2, y, resolve, reject)
},
r => {
reject(r)
}
)
} else {
resolve(x)
}
} catch (err) {
reject(err)
}
} else {
resolve(x)
}
}
此处的x是onFulfilled或者onRejected执行的结果。
- 如果x不是一个对象或者函数,直接resolve就可。 (PromiseA+ 2.3.4)
- 如果x是一个对象或者函数
- 如果x含有then方法,执行then方法(第一个参数onFulfilled, 第二个onRejected)。(PromiseA+ 2.3.3.3)
- 如果onFulfilled被以y作为参数调用了,在onFulfilled内部递归执行resolvePromise方法(此处主要是防止y也是一个promise)。 (PromiseA+ 2.3.3.3.1)
- 如果onRejected被以e作为参数调用了,直接用相同的reason rejected promise2。 (PromiseA+ 2.3.3.3.2)
- 如果onFulfilled和onRejected都被调用了,只执行第一个,其他的忽略掉。这个其实不需要担心,因为我们在之前实现Promise的时候设定状态是不可逆的,所以在此不需要做任何操作。 (PromiseA+ 2.3.3.3.3)
- 如果在整个过程中出现了异常,直接以此异常作为原因rejected promise2。 (PromiseA+ 2.3.3.3.4)
- 如果x没有then方法,直接resolve就可。 (PromiseA+ 2.3.3.4)
- 如果x含有then方法,执行then方法(第一个参数onFulfilled, 第二个onRejected)。(PromiseA+ 2.3.3.3)
还有一个小问题就是promise2和x不能是同一个,如果是同一个就会报错。 (PromiseA+ 2.3.1)
所以我们再多做一个小处理
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
...
}
完成了这个辅助方法,我们再返回来完善一下我们的then方法。
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = isFunction(onFulfilled) ? onFulfilled : data => data
onRejected = isFunction(onRejected) ? onRejected : err => { throw err }
const { status, value, reason } = this
let promise2 = new Promise((resolve, reject) => {
switch (status) {
case PROMISE_STATUS.FULFILLED:
runResolvePromise(promise2, onFulfilled(value), resolve, reject)
break
case PROMISE_STATUS.REJECTED:
runResolvePromise(promise2, onRejected(reason), resolve, reject)
break
case PROMISE_STATUS.PENDING:
this.rejectedCallbacks.push( reason => runResolvePromise(promise2, onRejected(reason), resolve, reject))
this.fulfilledCallbacks.push( value => runResolvePromise(promise2, onFulfilled(value), resolve, reject))
}
})
return promise2
}
上面我们说过, 如果在执行onFulfilled或者onRejected抛出一个异常e,promise2的状态变为rejected,并且以e作为异常的原因。 (PromiseA+ 2.2.7.2), 所以我们需要对他们的执行做tryCatch的处理。因为他们执行了多次,所以我们这里写一个帮助方法来一次性捕获错误,这样不需要写多个tryCatch。
function runResolvePromiseWithErrorCapture(promise, onFulfilledOrOnRejected, resolve, reject, valueOrReason) {
try {
let x = onFulfilledOrOnRejected(valueOrReason)
resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e)
}
}
最终的then方法是这样子的:
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = isFunction(onFulfilled) ? onFulfilled : data => data
onRejected = isFunction(onRejected) ? onRejected : err => { throw err }
const { status, value, reason } = this
let promise2 = new Promise((resolve, reject) => {
switch (status) {
case PROMISE_STATUS.FULFILLED:
setTimeout(() => {
runResolvePromiseWithErrorCapture(promise2, onFulfilled, resolve, reject, this.value)
}, 0)
break
case PROMISE_STATUS.REJECTED:
setTimeout(() => {
runResolvePromiseWithErrorCapture(promise2, onRejected, resolve, reject, this.reason)
}, 0)
break
case PROMISE_STATUS.PENDING:
this.rejectedCallbacks.push(reason => runResolvePromiseWithErrorCapture(promise2, onRejected, resolve, reject, reason))
this.fulfilledCallbacks.push(value => runResolvePromiseWithErrorCapture(promise2, onFulfilled, resolve, reject, value))
}
})
return promise2
}
使用setTimeout是因为此处的runResolvePromiseWithErrorCapture是立即执行的,但是onFulfilled(value)的执行可能是异步的,因此我们拿不到promise2。这里借助setTimeout来延迟执行。
写到这里其实一个符合PromiseA+规范的类已经实现了,我们可以使用promises-aplus-tests来测试是否符合规范,具体步骤请查看链接。
promise.catch()
catch方法只是用来捕获错误,也就是then方法的第一个参数为空。
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
promise.finally()
finally方法是在不论promise成功或者失败都会被调用的方法,比如我们有一个Http请求,请求之前我们打开了一个loading,那么不管是这个请求成功或者失败,在请求结束之后我们都要关闭这个loading,finally就可以用来做这个事情。
Promise.prototype.finally = function (callback) {
return this.then(
value => Promise.resolve(callback()).then(() => value),
err => Promise.resolve(callback()).then(() => { throw err })
)
}
静态方法
Promise.resolve()
resolve方法返回一个成功状态的Promise
Promise.resolve = function (value) {
return new Promise(resolve => resolve(value))
}
Promise.reject()
reject方法返回一个失败状态的Promise
Promise.reject = function (reason) {
return new Promise((resolve, reject) => reject(reason))
}
Promise.all()
all方法接收一个数组为参数。
- 如果传入的不是数组,直接将promise以空数组resolve。
- 首先遍历数组,如果item不是promise,直接将item作为最终的结果存在数组相应的位置,如果是promise,等待执行完毕,成功后将值存在数组相应的位置。
- 必须等所有的promise执行结束后才结束。
- 只要有一个promise失败,则整个失败。
Promise.all = function (promises) {
promises = isArray(promises) ? promises : []
let fulfilledCount = 0
let promisesLength = promises.length
let results = new Array(promisesLength)
return new Promise((resolve, reject) => {
if (promisesLength === 0) return resolve([])
promises.forEach((promise, index) => {
if (isPromise(promise)) {
promise.then(
value => {
results[index] = value
if (++fulfilledCount === promisesLength) resolve(results)
},
err => reject(err)
)
} else {
results[index] = promise
if (++fulfilledCount === promisesLength) resolve(results)
}
})
})
}
Promise.race()
race方法和all方法相同,接收一个数组作为参数,返回一个新的promise。
- 如果数组中哪一个promise的状态变为了成功,则新的promise直接变为成功,不需要等待其他的。
- 只要有一个promise失败,则新的promise直接变为失败。
Promise.race = function (promises) {
promises = isArray(promises) ? promises.filter(isPromise) : []
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(value => resolve(value), err => reject(err))
})
})
}
Promise.defer/Promise.deferred
这是一个帮助方法,如果你不喜欢使用new关键字来写,可以使用这个方法。
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
const p1 = Promise.defer()
fetch(url).then(res => {
p1.resolve(res)
})
p1.promise.then(res => {
// do something
}
)
结语
完整的源码在我的github promise。 如果各位同学有什么建议或者问题,欢迎留言讨论。