首先,Promise 中有三种状态:pending(等待态)、fulfilled(完成态)、rejected(拒绝态),并且这只能从 pending 到 fulfilled 或者从 pending 到 rejected,并且此过程不可逆。
我们先来实现 Promise 的基本结构:
当我们 new
一个 Promise
时,会传入一个执行函数:new Promise((resolve, reject) => {})
这个执行函数里有两个参数,resolve、reject 方法都是在 Promise 类里定义的,外部调用这个方法时,还可以往里面传入参数,Promise 拿到这个值后保存起来,以供 .then 调用
.then 方法需要指定成功的回调和失败的回调,根据 state 状态的不同来执行对应的回调
class Promise{
constructor(executor){
this.state = "pending" // 初始为 pending 态
this.value = ''
this.reson = ''
const resolve = (value) => {
this.value = value // 将传入的 value 保存起来,以便 then 调用
if(this.state === 'pending'){ // 只能从 pending 变到 fulfilled
this.state = 'fulfilled' // resolve 后,将状态置为 fulfilled
}
}
const reject = (reson) => {
this.reson = reson
if(this.state === 'pending'){
this.state = 'rejected'
}
}
executor(resolve, reject)
}
then(onfulfilled, onrejected){
if(this.state === 'fulfilled'){
onfulfilled(this.value) // 将之前 resolve 的值传入
}else if(this.state === 'rejected'){
onrejected(this.reson) // 将之前 reject 的值传入
}
}
}
以上,我们实现了两个功能:
- 实现了 Promise 基本结构和状态切换
- 实现基本的 then 方法,根据状态判断执行的回调
但是,上面的 Promise 还不能支持异步代码。
因为当 then 执行时,只对 fulfilled、rejected
两种状态做了判断,如果代码是异步的话,当执行到 then 方法时,resolve、reject
方法还没执行,还是 pending
态,所以我们这里需要对 pending
态做处理:
暂时将回调保存起来,等到 resolve、reject
执行的时候,才去执行对应的回调
class Promise{
constructor(executor){
this.state = "pending"
this.value = ''
this.reson = ''
this.onfulfilledCallbacks = [] // 存放 .then 中成功的回调
this.onrejectedCallbacks = [] // 存放 .then 中失败的回调
const resolve = (value) => {
this.value = value
if(this.state === 'pending'){
this.state = 'fulfilled'
}
this.onfulfilledCallbacks.forEach(fn => fn(this.value)) // 当 resolve 执行时,执行 then 中指定的成功回调
}
const reject = (reson) => {
this.reson = reson
if(this.state === 'pending'){
this.state = 'rejected'
this.onrejectedCallbacks.forEach(fn => fn(this.reson))
}
}
executor(resolve, reject)
}
then(onfulfilled, onrejected){
if(this.state === 'fulfilled'){
onfulfilled(this.value)
}else if(this.state === 'rejected'){
onrejected(this.reson)
}else if(this.state === 'pending'){ // 当 state 还未变化时,先将成功和失败的回调存起来
this.onfulfilledCallbacks.push(onfulfilled)
this.onrejectedCallbacks.push(onrejected)
}
}
}
以上,我们已经实现了:
- 实现了 Promise 基本结构和状态切换
- 实现基本的 then 方法,根据状态判断执行的回调
- 支持 Promise 异步
但是目前我们的 .then
只能调用一次,还不能实现链式调用 .then
,要实现也很简单,只需要让 then
方法返回一个 promise 实例即可。
但是注意:这里返回的 promise 实例必须是一个全新的 promise,这样才能保证后续 then
中的状态可以改变,不然的话从 new
完 Promise
之后状态就一直保持成一个了。。。
同时,then
的回调执行的返回值还会传给下一个 then
,所以还要把返回值 resolve
出去
then(onfulfilled, onrejected){
const promise2 = new Promise((resolve, reject) => {
if(this.state === 'fulfilled'){
const x = onfulfilled(this.value)
resolve(x)
}else if(this.state === 'rejected'){
const r = onrejected(this.reson)
resolve(r)
}else if(this.state === 'pending'){
this.onfulfilledCallbacks.push((value) => {
const x = onfulfilled(value)
resolve(x) // 将回调执行的返回值 resolve 出去,resolve 的值会挂载在返回的新的 promise 实例上的 value 属性上,也就是调用了这个实例里的 resolve 方法。下一个 then(也就是返回的这个新 promise 实例的 then) 可以拿到这个值
})
this.onrejectedCallbacks.push((reson) => {
const r = onrejected(reson)
resolve(r) // 注意:即使是失败的回调,回调执行完成后,下一个状态依然是 fulfilled,除非出错!(后面我们将处理这个问题)
})
}
})
return promise2 // 返回一个全新的 promise 实例
}
以上,我们已经实现了:
- 实现了 Promise 基本结构和状态切换
- 实现基本的 then 方法,根据状态判断执行的回调
- 支持 Promise 异步
- 每个 then 返回一个新 promise 实例,同时将回调执行的返回值
resolve
出去
我们现在来考虑一个问题,上面 then
中将回调执行的返回值 resolve
直接出去了,如果返回值是一个普通的值的话,这样没有问题。
但是,假如是个 promise
,那就不能这样直接 resolve
了
如果返回值是个 promise
实例,我们就必须调用这个 promise
的 then
,让这个 promise
执行,拿到这个 promise
resolve
的值
我们需要写一个方法来处理返回值
then(onfulfilled, onrejected){
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => { // 这里加定时器,为了让里面的代码异步,保证传入 promise2 的时候, promise2 已经初始化完了,这也就解释了为什么 Promise 的 then 方法是异步的
if(this.state === 'fulfilled'){
const x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
}
else if(this.state === 'rejected'){
const r = onrejected(this.reson)
resolvePromise(promise2, r, resolve, reject)
}
else if(this.state === 'pending'){
this.onfulfilledCallbacks.push((value) => {
const x = onfulfilled(value)
resolvePromise(promise2, x, resolve, reject) // 用特定的方法处理返回值
})
this.onrejectedCallbacks.push((reson) => {
const r = onrejected(reson)
resolvePromise(promise2, r, resolve, reject)
})
}
}, 0)
})
return promise2
}
function resolvePromise(promise2, x, resolve, reject) {
// 判断 x,如果是一般值直接 resolve,如果是一个 promise,要等待 promise resolve 或 reject
if (promise2 === x) {
return new TypeError('循环引用!')
}
else if (x instanceof Promise) { // x 是 promise 实例
x.then(y => {
resolvePromise(promise2, y, resolve, reject) // 此时的 y 可能还是一个 promise,所以需要递归调用 resolvePromise,直到解析出一个普通值,就将这个值通过 "传进来的 promise2 的 resolve 方法" 传出去
}, r => {
reject(r) // x 内部 reject 了或出错了
})
}
else { // 普通值,直接 resolve
resolve(x)
}
}
这段代码需要注意的地方比较多:
- 为了保证
promise2
可以拿到,加了一个定时器使代码异步 - 对 then 中所有 resolve 的值都需要采用
resolvePromise
方法,因为回调函数的返回值可能是 promise 实例 - 在
resolvePromise
方法中,需要校验是否循环引用,同时需要注意 promise resolve 的值还是 promise 的情况
以上,我们实现的功能有:
- 实现了 Promise 基本结构和状态切换
- 实现基本的 then 方法,根据状态判断执行的回调
- 支持 Promise 异步
- 每个 then 返回一个新 promise 实例,对回调执行的返回值进行判断,如果是普通值就直接
resolve
出去,如果是一个 promise 实例,就执行 then 方法,直到得到一个普通值。同时,我们让 then 方法变成异步的了
接下来要考虑的就是,当我们第一次 new Promise
的时候,如果 resolve 的值也是一个 promise,也需要等待这个 promise 执行完 then,如下:
new Promise((resolve, reject) => {
resolve(Promise.resolve(123))
})
.then(d => {
console.log(d) // 123
})
所以我们需要对 resolve 方法进行完善
const resolve = (value) => {
if(value instanceof Promise){// 第一次 new Promise 时,resolve 的值如果是一个 promise
// 调用 then,传入 resolve,resolve 中又进行是否为 promise 的判断调用,递归调用直到 value 不是一个 promise
return value.then(resolve, reject)
}
if (this.state === 'pending') { // 只有 pedding 态可修改,并且不可逆
this.state = 'fulfilled'
this.value = value
this.onfulfilledCallbacks.forEach(fn => fn(value)) // 执行 resolve 回调
}
}
以上,我们实现的功能有:
- 实现了 Promise 基本结构和状态切换
- 实现基本的 then 方法,根据状态判断执行的回调
- 支持 Promise 异步
- 每个 then 返回一个新 promise 实例,对回调执行的返回值进行处理。同时,让 then 方法变成异步
- 对第一次
new Promise
时 resolve 的值进行 Promise / 非Promise 的处理
回顾一下我们上面的代码,好像一直没有出现 reject 的情况,在 Promise 中,出现 reject 的情况有几种:
- 发生错误
- 主动 reject
我们对代码中需要进行错误捕获的地方做一下处理,顺便把 catch 方法实现了
class Promise {
constructor(executor) {
this.state = "pending"
this.value = ''
this.reson = ''
this.onfulfilledCallbacks = []
this.onrejectedCallbacks = []
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onfulfilledCallbacks.forEach(fn => fn(value))
}
}
const reject = (reson) => {
this.reson = reson
if (this.state === 'pending') {
this.state = 'rejected'
this.onrejectedCallbacks.forEach(fn => fn(this.reson))
}
}
try { // 错误捕获
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onfulfilled, onrejected) {
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
if (this.state === 'fulfilled') {
try { // 错误捕获
const x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
else if (this.state === 'rejected') {
try { // 错误捕获
const r = onrejected(this.reson)
resolvePromise(promise2, r, resolve, reject)
} catch (e) {
reject(e)
}
}
else if (this.state === 'pending') {
this.onfulfilledCallbacks.push((value) => {
try { // 错误捕获
const x = onfulfilled(value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
this.onrejectedCallbacks.push((reson) => {
try { // 错误捕获
const r = onrejected(reson)
resolvePromise(promise2, r, resolve, reject)
} catch (e) {
reject(e)
}
})
}
},0)
})
return promise2
}
catch(rejectFn){ // 错误处理函数
return this.then(null, rejectFn) // catch 其实就是第一个参数为 null 的 then 方法
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return new TypeError('循环引用!')
}
else if (x instanceof Promise) {
x.then(y => {
resolvePromise(promise2, y, resolve, reject)
},
r => {
reject(r)
})
}
else {
resolve(x)
}
}
我们已经基本完善我们的 Promise 了,不过还需要考虑一种极端情况:
假如我中间传了一个空的 then,new Promise 中 resolve 的值仍然可以被传递下去,被下一个 then 拿到并打印。
new Promise((resolve, reject) => {
resolve(123)
})
.then()
.then(v => {
console.log(v)
})
这就需要给 then 传一个默认的函数参数,不传的话默认将拿到的值 return 出去
很简单,一行搞定
then(onfulfilled = v=>v, onrejected = r=>r){
// ...
}
以上,我们的 Promise 就实现得差不多了,回顾一下我们实现的思路:
- 实现了 Promise 基本结构和状态切换
- 实现基本的 then 方法,根据状态判断执行的回调
- 支持 Promise 异步
- 每个 then 返回一个新 promise 实例,对回调执行的返回值进行 Promise / 非Promise 的处理。同时,让 then 方法变成异步
- 对第一次
new Promise
时 resolve 的值进行 Promise / 非Promise 的处理 - 给 Promise 内部添加错误处理和 catch 方法
- 给 then 设置默认参数,不传的时候默认把值传递下去
还剩下一些 Promise 类的静态方法,这里我们也一并实现了:
Promise.resolve = function(value){
// Promise.resolve 实际上就是创建一个新的 Promise 实例返回,同时将传入的 value 参数 resolve 出去
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.reject = function(reson){
return new Promise((resolve, reject) => { // Promise.reject 原理同上
reject(reson)
})
}
这里着重讲一下 Promise.all
和 Promise.race
方法
- Promise.all:
Promise.all
接收一个任务数组,数组元素 (可能是promise
或其他值) 并发执行,如果是Promise
就执行他,拿到resolve
的值,如果是其他普通值就直接存起来,执行完成后的值存放在一个结果数组中
Promise.all
执行后会返回一个 新的Promise
实例,并且会将结果数组resolve
出去,下一个then
中可以拿到
很明显,我们只需要在内部实现一个计数器,每个任务元素完成后将计数器加 1,只要达到了任务数组的 length 长度即可
- Promise.race
这个方法就是:比比谁最快执行完
遍历数组参数,执行每一个元素 (同样的,注意区分 Promise 和 非Promise)
对于 Promise 实例,Promise 执行完成后,直接 resolve
对于普通值,直接 resolve
Promise.all = function (p) {
return new Promise((resolve, reject) => {
let count = 0
const result = [] // 结果数组
function processData(index, value) { // 存放每个 任务执行完后的值,并计数,计数完成后将 result 数组 resolve 出去
result[index] = value
if (++count === p.length) {
resolve(result)
}
}
p.forEach((cur, index) => {
if (cur instanceof Promise) { // promise 实例
cur.then(v => {
processData(index, v)
}, r => {
reject(r) // 只要任何一个 promise 出错,就 reject
})
} else { // 普通值
processData(index, cur)
}
})
})
}
Promise.race = function (p) {
return new Promise((resolve, reject) => { // 只要一个完成了,直接 resolve,resolve 出来的值就是最快执行完的
p.forEach(cur => {
if(cur instanceof Promise){
cur.then(r => { // 执行 promise 然后 resolve
resolve(r)
})
}else { // 不是 promise,直接 resolve
resolve(cur)
}
})
})
}
以上就完成了对 Promise 的编写,完整代码如下 (无注释版):
class Promise {
constructor(executor) {
this.state = "pending"
this.value = ''
this.reson = ''
this.onfulfilledCallbacks = []
this.onrejectedCallbacks = []
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onfulfilledCallbacks.forEach(fn => fn(value))
}
}
const reject = (reson) => {
this.reson = reson
if (this.state === 'pending') {
this.state = 'rejected'
this.onrejectedCallbacks.forEach(fn => fn(this.reson))
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onfulfilled = v=>v, onrejected = r=>r) {
const promise2 = new Promise((resolve, reject) =>{
setTimeout(() =>{
if (this.state === 'fulfilled') {
try {
const x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
else if (this.state === 'rejected') {
try {
const r = onrejected(this.reson)
resolvePromise(promise2, r, resolve, reject)
} catch (e) {
reject(e)
}
}
else if (this.state === 'pending') {
this.onfulfilledCallbacks.push((value) =>{
try {
const x = onfulfilled(value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
this.onrejectedCallbacks.push((reson) =>{
try {
const r = onrejected(reson)
resolvePromise(promise2, r, resolve, reject)
} catch (e) {
reject(e)
}
})
}
},0)
})
return promise2
}
catch(rejectFn){
return this.then(null, rejectFn)
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return new TypeError('循环引用!')
}
else if (x instanceof Promise) {
x.then(y => {
resolvePromise(promise2, y, resolve, reject)
},
r => {
reject(r)
})
}
else {
resolve(x)
}
}
Promise.resolve = function(value){
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.reject = function(reson){
return new Promise((resolve, reject) => {
reject(reson)
})
}
Promise.all = function (p) {
return new Promise((resolve, reject) => {
let count = 0
const result = []
function processData(index, value) {
result[index] = value
if (++count === p.length) {
resolve(result)
}
}
p.forEach((cur, index) => {
if (cur instanceof Promise) {
cur.then(v => {
processData(index, v)
}, r => {
reject(r)
})
} else {
processData(index, cur)
}
})
})
}
Promise.race = function (p) {
return new Promise((resolve, reject) => {
p.forEach(cur => {
if(cur instanceof Promise){
cur.then(r => {
resolve(r)
})
}else {
resolve(cur)
}
})
})
}
感谢你看到这里 (▽)
欢迎关注更多个人博客
本文正在参与“写编程博客瓜分千元现金”活动,关注公众号“饥人谷”回复“编程博客”参与活动。