本文将带领大家实现一个简单的Promise,在阅读本文之前,默认您对Promise有基本的了解,会基本的使用,以及对ES6的语法有一定的了解
1. Promise的构造函数
首先,我们从Promise的构造函数开始,这是使用Promise最关键的一步
// 创建三个常量,用于表示Promise的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise类
class Promise {
// Promise构造函数会传入一个方法
constructor(executor) {
// Promise中存在一个保存状态和保存状态结果的变量
this.promiseState = PENDING
this.promiseResult = null
// 创建Promise要传入的两个函数,用于将状态变为成功和失败的函数
const resolve = res => {
// 我们使用了箭头函数,方便绑定this
// 成功的函数,用于将Promise状态修改为FULFILLED,并保存成功值
// 我们知道,如果Promise状态已改变,就不能再改变了,所以要做一个检测
if (this.promiseState !== PENDING) return
this.promiseState = FULFILLED
this.promiseResult = res
}
const reject = err => {
if (this.promiseState !== PENDING) return
this.promiseState = REJECTED
this.promiseResult = err
}
// 最后,我们需要调用executor,传入两个改变状态的函数,并进行异常处理
try {
executor(resolve, reject)
} catche (err) {
// 出现异常,我们就调用reject函数,改变Promise状态为失败即可
reject(err)
}
}
}
上面,我们就实现了一个基本的构造函数,但是,上面还只能处理executor是同步改变状态的情况,如果是异步调用的resolve或reject,则不行,我们后续在编写then函数时会完善这个功能
2. 静态方法resolve
因为在then函数中,我们将使用这个方法,所以首先编写这个函数
在编写之前,我们要知道Promise.resolve函数对于不同传入参数的处理,这个函数是用来包装一个值称为Promise对象,但根据不同的参数值传入,会进行不同的处理
1、传入的是一个Promise对象
如果传入的是一个Promise对象,那么将会幂等的返回一个Promise对象,其实意思就是返回这个Promise对象本身
2、传入的是一个thenable对象
thenable对象的含义就是,对象中拥有then方法,此时,这个then方法就跟Promise构造函数传入的executor拥有同样的功能,即这个then方法在参数中会分别接收到resolve和reject方法,可以用来改变Promise对象的状态
3、传入其他的值
如果不是以上两种类型,Promise会创建一个新的Promise对象,将这个新Promise的状态改变为fulfilled,并将状态结果设置为传入的值,即Promise会包装这个值
有了以上的分析,我们就来写一写这个方法
class Promise {
static resolve(wrapped) {
if (wrapped instanceof Promise) {
// 如果是Promise对象
return wrapped
} else if (wrapped instanceof Object && wrapped.then instanceof Function) {
// 是一个thenable对象,then方法就相当于executor函数
return new Promise(wrapped.then)
} else {
// 其他值直接包装
return new Promise(resolve => resolve(wrapped))
}
}
}
当然,上面代码如果要正常运行,得在我们的Promise实现完成之后,因为代码中使用到了Promise,我们之所以先编写这个函数,是为了方便then函数的编写
3. 静态方法reject
既然我们都编写了resolve,那干脆reject的一起写了吧,reject并不像resolve函数一样,resolve函数是幂等的,但reject函数,不论接收到什么值,都会将这个值包装成一个拒绝的Promise对象,话不多说,直接写上代码
class Promise {
static reject(wrapped) {
return new Promise((resolve, reject) => reject(wrapped))
}
}
一行代码就搞定,非常简单是吧
4. then方法,Promise的核心
上面都只是开胃菜,then方法才是我们的重中之重,在书写then方法前,我们先理一理then方法中的一些规则
1、首先,then方法可以传入两个方法,第一个是解决执行的回调,第二个是拒绝执行的回调
2、then方法返回一个Promise对象,根据解决或拒绝的回调函数的返回值决定这个返回的Promise对象的状态
3、解决或拒绝函数中的异常,会导致返回的Promise对象为拒绝状态
4、值传递和异常穿透(后面写的时候会解释)
接下来,我们以代码注释的形式,在代码中将这些功能体现出来,包括一些异步状态改变处理,都会在代码注释中解释
class Promise {
// 首先是传入参数,一共是两个,实现了我们说的第一条规则
then(onFulfilled, onRejected) {
// 这里创建了一个新的Promise对象,作为函数的返回值
// 这个Promise对象的状态,由参数的两个处理函数的返回值决定
// 我们在函数最后返回了这个值,实现了上述的第二条规则
const resultPromise = new Promise((resolve, reject) => {
// 此处便实现了第三条规则的值传递
// 如果传入的解决的回调函数参数不是一个函数
// 那么返回的Promise对象的状态和值便需要同步未处理的值
// 以便值能传递到这个返回的Promise对象的解决的then方法的回调中
if (typeof onFulfilled !== 'function ') {
onFulfilled = resolve
}
// 实现异常穿透跟值传递也是一样
// 需要将异常传递到下一个then函数的回调中去
if (typeof onRejected !== 'function ') {
onRejected = reject
}
// 接下来就是对于这个两个回调函数的调用
if (this.promiseState === PENDING) {
// 建议可以先看一下下面解决和拒绝状态的处理,再来看等待状态
// 如果执行到then方法,状态还是等待,说明状态是异步改变的
// 此时我们就需要把成功和失败的回调保存下来,等到状态改变的时候再调用
// 我们就可以在构造函数中多创建一个数组,用来专门保存这个要异步执行的回调函数
// 需要在构造函数中改变的代码,将在then方法后面写出
this.promiseCallbacks.push({
// 这个onFulfilled只是对象的键名,不要和then传入的onFulfilled混淆了
onFulfilled: () => {
let value
try {
// 此处调用的是then传入的onFulfilled
value = onFulfilled(this.promiseResult)
} catch (err) {
// 如果出现异常,那么就把返回的Promise的状态同步为拒绝
reject(err)
}
Promise.resolve(value).then(resolve, reject)
},
onRejected: () => {
let value
try {
// 此处调用的是then传入的onRejected
value = onRejected(this.promiseResult)
} catch (err) {
// 如果出现异常,那么就把返回的Promise的状态同步为拒绝
reject(err)
}
Promise.resolve(value).then(resolve, reject)
}
})
} else if (this.promiseState === Promise.FULFILLED) {
// 如果在执行then方法中回调时,状态是解决,直接执行回调
// 当然执行回调要进行异常处理,以及返回Promise状态的同步
let value
try {
value = onFulfilled(this.promiseResult)
} catch (err) {
// 如果出现异常,那么就把返回的Promise的状态同步为拒绝
reject(err)
}
// 我们此处就巧妙的使用静态方法resolve,来同步返回的Promise的值和状态
// 以为then传入的回调函数的返回值的处理,和resolve的处理是一样
// 所以我们可以使用resolve包装这个返回值,都会返回每种情况包装后的Promise对象
// 然后我们就只需要使用then方法,再将返回的Promise对象的resolve和reject传入then中
// 那么就能实现返回的Promise对象的状态同步then方法回调函数的返回值的情况
Promise.resolve(value).then(resolve, reject)
} else if (this.promiseState === Promise.REJECTED) {
// 状态为拒绝的情况,跟上面一样,除了调用的回调函数不一样
let value
try {
value = onRejected(this.promiseResult)
} catch (err) {
reject(err)
}
Promise.resolve(value).then(resolve, reject)
}
})
return resultPromise
}
// 这里是上述改变后的构造函数的代码,我们删除了不必要的注释
constructor(executor) {
this.promiseState = PENDING
this.promiseResult = null
// 这里就加入一个保存回调的数组,用于保存回调函数
// 您可能会疑惑为什么要用一个数组来保存
// 因为一个Promise对象可以多次调用then方法,所以就可以又多个回调函数需要保存
this.promiseCallbacks = []
const resolve = res => {
if (this.promiseState !== PENDING) return
this.promiseState = FULFILLED
this.promiseResult = res
// 成功的处理就应该调用所有成功的回调函数
this.promiseCallbakcs.forEach(item => item.onFulfilled())
}
const reject = err => {
if (this.promiseState !== PENDING) return
this.promiseState = REJECTED
this.promiseResult = err
// 失败的处理就应该调用所有失败的回调函数
this.promiseCallbakcs.forEach(item => item.onRejected())
}
try {
executor(resolve, reject)
} catche (err) {
reject(err)
}
}
}
看了上面代码,咱们可能发现,有需要重复的代码,比如在处理异常和同步状态的地方,有些非常高的相似度,所以我们将代码优化一下,将重复的地方封装一下
// 首先,我们也是将不必要的注释删除,并将能单行书写的代码也改为单行
class Promise {
then(onFulfilled, onRejected) {
const resultPromise = new Promise((resolve, reject) => {
// 此处我们写一个函数,整合异常处理和返回值同步的地方
// 参数handler代码要执行的函数,如onFulfilled或onRejected
const resultHandler = handler => {
// 这里使用一个setTimeout,就是模拟了then方法回调函数的异步执行
// 在Promise中then方法的回调函数是异步的微任务,我们这里用这个模拟一下异步
setTimeout(() => {
// 这里面的结构就跟之前的处理是一样的
// 只是把要调用的函数,换成了传入的handler
let value
try {
value = handler(this.promiseResult)
} catch (err) {
reject(err)
}
// 这里我们加入一个循环引用的检测
// 防止then回调函数中返回自己返回的Promise对象
if (callbackReturn === returnPromise) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
}
Promise.resolve(value).then(resolve, reject)
})
}
if (typeof onFulfilled !== 'function ') onFulfilled = resolve
if (typeof onRejected !== 'function ') onRejected = reject
if (this.promiseState === PENDING) {
this.promiseCallbacks.push({
// 下面这些地方就会变得很简单,只能传入对应的处理函数即可
onFulfilled: () => resultHandler(onFulfilled)
onRejected: () => resultHandler(onRejected)
})
} else if (this.promiseState === Promise.FULFILLED) {
resultHandler(onFulfilled)
} else if (this.promiseState === Promise.REJECTED) {
resultHandler(onRejected)
}
})
return resultPromise
}
}
到这里,是不是发现其实也不难,只要对Promise功能有一定的了解,相信大家自己总结一下也能够将它写出来
5. catch方法
有了上面的函数,剩下的方法实现起来都会变得非常容易
class Promise {
catch(onRejected) {
// catch方法其实就是用来处理异常穿透下来的异常,所以它跟then方法中onRejected的功能是一样的
// 所以我们可以直接使用then方法来完成它的功能
return this.then(null, onRejected)
}
}
6. finally方法
class Promise {
finally(onFilnally) {
// finally也同样,但是无论解决还是拒绝,都调用同样的方法
// 但是finally回调函数不需要传入值,所以使用一个箭头函数包装一下
return this.then(() => onFilnally(), () => onFilnally())
}
}
7. 静态方法all
all方法可以接收一个数组,其中的值如果是Promise对象,则只有当所有对象是解决状态时,这个all方法才会返回一个解决的值,如果有任何一个Promsie对象被拒绝,那么all方法返回的Promise也是拒绝的,并且拒绝理由就是这个被拒绝的Promise的理由,如果参数数组中有的元素不是Promise对象,则会被包装成Promise对象
class Promise {
static all(promises) {
// 我们就默认传入的是数组,省去一些检查的步骤
return new Promise((resolve, reject) => {
// 放置结果的数组
let ret = []
// 用于判断是否数组中所有promise对象都处理完毕
let count = 0
// 我们就应该遍数组,去处理数组中的每一个Promise对象
for (let index = 0; index < promises.length; ++index) {
// 我们使用Promise.resolve包装值,让所有元素都称为Promise对象
Promise.resolve(promises[index]).then(
res => {
// 如果成功,存储其成功值
count++
ret[index] = res
if (count === promises.length) {
// 如果全部都解决,则返回的Promise为解决
resolve(ret)
}
}, err => {
// 如果有任何一个失败,则返回的Promise为失败
reject(err)
}
)
}
})
}
}
8. 静态方法race
race方法相对all就更加简单,谁快就是谁
class Promise {
static race(promises) {
// 我们就默认传入的是数组,省去一些检查的步骤
return new Promise((resolve, reject) => {
for (let index = 0; index < promises.length; ++index) {
Promise.resolve(promises[index]).then(
res => {
resolve(res)
}, err => {
reject(err)
}
)
}
})
}
}
9. 整理所有代码
下面列出所有代码的整合,删除了注释
class Promise {
constructor(executor) {
this.promiseState = PENDING
this.promiseResult = null
this.promiseCallbacks = []
const resolve = res => {
if (this.promiseState !== PENDING) return
this.promiseState = FULFILLED
this.promiseResult = res
this.promiseCallbakcs.forEach(item => item.onFulfilled())
}
const reject = err => {
if (this.promiseState !== PENDING) return
this.promiseState = REJECTED
this.promiseResult = err
this.promiseCallbakcs.forEach(item => item.onRejected())
}
try {
executor(resolve, reject)
} catche (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
const resultPromise = new Promise((resolve, reject) => {
const resultHandler = handler => {
setTimeout(() => {
let value
try {
value = handler(this.promiseResult)
} catch (err) {
reject(err)
}
if (callbackReturn === returnPromise) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
}
Promise.resolve(value).then(resolve, reject)
})
}
if (typeof onFulfilled !== 'function ') onFulfilled = resolve
if (typeof onRejected !== 'function ') onRejected = reject
if (this.promiseState === PENDING) {
this.promiseCallbacks.push({
onFulfilled: () => resultHandler(onFulfilled)
onRejected: () => resultHandler(onRejected)
})
} else if (this.promiseState === Promise.FULFILLED) {
resultHandler(onFulfilled)
} else if (this.promiseState === Promise.REJECTED) {
resultHandler(onRejected)
}
})
return resultPromise
}
static resolve(wrapped) {
if (wrapped instanceof Promise) {
return wrapped
} else if (wrapped instanceof Object && wrapped.then instanceof Function) {
return new Promise(wrapped.then)
} else {
return new Promise(resolve => resolve(wrapped))
}
}
static reject(wrapped) {
return new Promise((resolve, reject) => reject(wrapped))
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(onFilnally) {
return this.then(() => onFilnally(), () => onFilnally())
}
static all(promises) {
return new Promise((resolve, reject) => {
let ret = []
let count = 0
for (let index = 0; index < promises.length; ++index) {
Promise.resolve(promises[index]).then(res => {
count++
ret[index] = res
if (count === promises.length) resolve(ret)
}, err => {
reject(err)
})
}
})
}
static race(promises) {
return new Promise((resolve, reject) => {
for (let index = 0; index < promises.length; ++index) {
Promise.resolve(promises[index]).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
}
10. 总结
到此为止,我们就实现了一个简单版本的Promise,相信在大家思考和练习之后,会发现,其实也不难嘛,也希望大家能从文章中学到东西,如果文章中有任何错误的地方,望各位大牛们能指点,谢谢大家。