promise原理

125 阅读8分钟

前言

想必大家对于promise的使用已经很熟练了,接下来就手写个promise吧。

promise的三种状态

接下来看一段代码

const promise1 = new Promise((resolve, reject) => {})

const promise2 = new Promise((resolve, reject) => {
  resolve('aaa')
  reject('bbb)
})

const promise3 = new Promise((resolve, reject) => {
  reject('error')
})

这段代码的执行结果是

image.png

不难发现,promise有三种状态。

  1. promise的初始状态为 pending,
  2. 执行resolve之后状态变为 fulfilled
  3. 执行reject之后状态变为 rejected
  4. 当状态发生转变后,不可再次转变

实现resolve,reject

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    this.status = 'pending' // promise默认状态
    this.value = undefined

    this.resolve = value => {
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
      }
    }

    this.reject = err => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.value = err
      }
    }

    // 在new proimse时会传入一个函数,接受两个参数,并立即执行
    executor(this.resolve, this.reject)
  }
}

简单的resolve,reject就实现了,看看效果吧

const promise = new MyPromise((resolve, reject) => {
  resolve('aaa')
  reject('error message')
})

console.log(promise)

结果如下

image.png

可以发现,当我们在resolve之后执行reject,promise的状态并没有改变,达到我们的目的。可是我们怎么拿到resolve或reject的值呢?一般是通过then函数来获取promise值,接下来就实现一个简单的then函数吧。

实现then

  1. 首先then接受两个函数参数
  2. 第一个参数是当promise状态为fulfilled时执行回调
  3. 第二个参数是当promise状态为rejected时执行回调
 then(onFulFilled, onRejected) {
    if (this.status === PENDING) {
    } else if (this.status === FULFILLED) {
      onFulFilled(this.value)
    } else if (this.status === REJECTED) {
      onRejected(this.value)
    }
  }

测试一下吧

const promise = new MyPromise((resolve, reject) => {
  resolve('aaa')
  reject('error message')
})

promise.then(
  res => {
    console.log('success:', res) // 当状态为fulfilled时,打印
  },
  err => {
    console.log('err:', err)  // 当状态为rejected时,打印
  }
)

我们经常在promise中进行异步操作,如ajax请求。但是then函数是同步进行的,当then函数执行时,promise的状态还是pending。要想在promise状态改变时执行then传入的函数,我们要在pending装填时保存then传入的函数,等异步代码执行的时候,再执行保存的函数。

class MyPromise {
  constructor(executor) {
    this.status = 'pending' // promise默认状态
    this.value = undefined
    // 保存then传入的函数
    this.onFulFilledFns = []
    this.onRejectedFns = []

    this.resolve = value => {
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
        this.onFulFilledFns.forEach(fn => {
          fn(this.value)
        })
      }
    }

    this.reject = err => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.value = err
        this.onRejectedFns.forEach(fn => {
          fn(this.value)
        })
      }
    }

    // 在new proimse时会传入一个函数,接受两个参数,并立即执行
    executor(this.resolve, this.reject)
  }

  then(onFulFilled, onRejected) {
    if (this.status === PENDING) {
      this.onFulFilledFns.push(onFulFilled)
      this.onRejectedFns.push(onRejected)
    } else if (this.status === FULFILLED) {
      onFulFilled(this.value)
    } else if (this.status === REJECTED) {
      onRejected(this.value)
    }
  }
}
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {  // 这里用setTimeout代替异步请求
    resolve('aaa')  
  }, 1000)
})

promise.then(
  res => {
    console.log('success:', res) // 一秒后成功执行
  },
  err => {
    console.log('err:', err)
  }
)

好了,普通的then调用已经实现了,但是,这与我们平时用的promise不一样啊。平时都是直接.then,可以链式调用,那接下来就分析一下怎么实现then的链式调用。

then的链式调用(核心)

根据我们平时使用promise,我们可以发现以下功能

  1. 调用then时返回一个新的promise对象
  2. 在resoolve或者reject中返回一个变量时,则会把这个值包装成promise,然后resolve出去,在下一个then回调中第一个函数就可以获取到。 好了,看下面代码
then(onFulFilled, onRejected) {
    // if (this.status === PENDING) {
    //   this.onFulFilledFns.push(onFulFilled)
    //   this.onRejectedFns.push(onRejected)
    // } else if (this.status === FULFILLED) {
    //   // fulfilled时
    //   onFulFilled(this.value)
    // } else if (this.status === REJECTED) {
    //   onRejected(this.value)
    // }
    
    // 发现上面代码和return出的promise代码重复,而且在new promise的时候,也会执行传入的函数
    return new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 如果then没有返回值,则直接是resolve(undefined)
        // 如果有返回值,则拿到then的第一个函数的返回值,然后resolve出去
        const value = onFulFilled(this.value) // onFulfilled没有返回值时,value为undefined
        resolve(value)
      } else if (this.status === REJECTED) {
        const error = onRejected(this.value)
        // 在rejected中的返回值,用resolve传递状态
        resolve(error)
      } else if (this.status === PENDING) {
        // 如果在new promise中,使用的是异步调用呢,promise的状态改变是异步的
        // then的回调时同步的
        // 我们要怎么拿到异步之后resolve或者reject的值呢?
        // 将回调函数存起来?如果函数有返回值,怎么拿到保存函数的返回值呢?我们可以给保存的函数再包一层函数,利用闭包来解决问题
        this.onFulFilledFns.push(() => {
          const value = onFulFilled(this.value)
          resolve(value)
        })
        this.onRejectedFns.push(() => {
          const error = onRejected(this.value)
          reject(error)
        })
      }
    })
  }

写个例子试试吧

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 1000)
})
  .then(
    res => {
      console.log('res1:', res)  // 成功打印
      return 'bbb'
    },
    err => {
      console.log('err1:', err)
    }
  )
  .then(
    res => {
      console.log('res2:', res)  // 成功打印
    },
    err => {
      console.log('err2:', err)
    }
  )

但是我们没有做异常捕获,我们再优化一下代码

// 工具函数
function executorFnWhenCatchError(fn, resolve, reject, value) {
  try {
    const res = fn(value)
    resolve(res)
  } catch (error) {
    reject(error)
  }
}
...
then(onFulFilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 如果then没有返回值,则直接是resolve(undefined)
        // 如果有返回值,则拿到then的第一个函数的返回值,然后resolve出去
        // onFulfilled没有返回值时,value为undefined
        executorFnWhenCatchError(onFulFilled, resolve, reject, this.value)
      } else if (this.status === REJECTED) {
        // 在rejected中的返回值,用resolve传递状态
        executorFnWhenCatchError(onRejected, resolve, reject, this.value)
      } else if (this.status === PENDING) {
        // 如果在new promise中,使用的是异步调用呢,promise的状态改变是异步的
        // then的回调时同步的
        // 我们要怎么拿到异步之后resolve或者reject的值呢?
        // 将回调函数存起来?怎么拿到保存函数的返回值呢?我们可以给保存的函数包一层函数
        this.onFulFilledFns.push(() => {
          executorFnWhenCatchError(onFulFilled, resolve, reject, this.value)
        })
        this.onRejectedFns.push(() => {
          executorFnWhenCatchError(onRejected, resolve, reject, this.value)
        })
      }
    })
  }

我们抛出个异常试试

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 1000)
})
  .then(
    res => {
      console.log('res1:', res)
      throw new Error('error message')
    },
    err => {
      console.log('err1:', err)
    }
  )
  .then(
    res => {
      console.log('res2:', res)
    },
    err => {
      console.log('err2:', err)  // 成功捕获到异常
    }
  )

实现catch

实现then的回到之后,catch就很简单了。

  1. catch传入一个函数,当捕获到错误时,如果没有then没有传入第二个参数,会执行catch回调函数
  2. catch后会返回一个promise

我们上面实现的then就是返回一个promise,而且也可以捕获异常,那么catch可以这样实现

 catch(onRejected) {
    return this.then(undefined, onRejected)
  }

看着是不是很简单,我们来试一下

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 1000)
})
  .then(
    res => {
      console.log('res1:', res)
      throw new Error('error message')
    },
    err => {
      console.log('err1:', err)
    }
  )
  .then(res => {
    console.log('res2:', res)
  })
  .catch(err => {
    console.log('err2:', err)
  })

不幸,报错了,fn不是一个函数。不难发现,我们catch前面的then回调中没有传入对异常的捕获函数。则在第一个then回调中抛出异常,则会遍历执行onRejectedFns里的函数,遇到undefined不是一个函数,即报错。为了解决这个问题,我们可以给错误回调一个默认值。

 then(onFulFilled, onRejected) {
    // 如果传入的onRejected函数为空,我们要想传递报错,则给默认函数,在默认函数中传递错误
    onRejected =
      onRejected ||
      (err => {
        throw new Error(err)
      })

    return new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 如果then没有返回值,则直接是resolve(undefined)
        // 如果有返回值,则拿到then的第一个函数的返回值,然后resolve出去
        // onFulfilled没有返回值时,value为undefined
        executorFnWhenCatchError(onFulFilled, resolve, reject, this.value)
      } else if (this.status === REJECTED) {
        // 在rejected中的返回值,用resolve传递状态
        executorFnWhenCatchError(onRejected, resolve, reject, this.value)
      } else if (this.status === PENDING) {
        // 如果在new promise中,使用的是异步调用呢,promise的状态改变是异步的
        // then的回调时同步的
        // 我们要怎么拿到异步之后resolve或者reject的值呢?
        // 将回调函数存起来?怎么拿到保存函数的返回值呢?我们可以给保存的函数包一层函数
        this.onFulFilledFns.push(() => {
          executorFnWhenCatchError(onFulFilled, resolve, reject, this.value)
        })
        this.onRejectedFns.push(() => {
          executorFnWhenCatchError(onRejected, resolve, reject, this.value)
        })
      }
    })
  }

好了catch就实现了

实现finally

在我们平时的使用过程中,finally不论是否成功都会执行。很简单,我们在then中传入两个相同的函数就可以了,这样不论成功还是失败,都会执行finally的回调函数。

  finally(onFulFilled) {
    this.then(onFulFilled, onFulFilled)
  }

还是会出现和catch一样的问题。我们在掉用catch的时候,传入的onFulFilled的为undefined,很简单,我们给onFulFilled函数一个默认值。

 onFulFilled =
      onFulFilled ||
      (value => {
        return value
      })

测试一下

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 1000)
})
  .then(
    res => {
      console.log('res1:', res)
      throw new Error('error message')
    },
    err => {
      console.log('err1:', err)
    }
  )
  .then(res => {
    console.log('res2:', res)
  })
  .catch(err => {
    console.log('err2:', err)
  })
  .finally(() => {
    console.log('finally')  // 成功执行
  })

实现静态方法resolve和reject

promise类中的静态方法,返回一个promise

 static resolve(value) {
    return new MyPromise((resolve, reject) => {
      resolve(value)
    })
  }

  static reject(value) {
    return new MyPromise((resolve, reject) => {
      reject(value)
    })
  }

实现race

实现上面的功能后,race就很简单了

  1. race接收一个promise数组,当其中有一个promise状态发生变化时,返回这个变化的promise值
  2. 通过then回调,拿到值
 static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(
          res => {
            resolve(res)
          },
          err => {
            resolve(err)
          }
        )
      })
    })
  }

测试一下

const promsie1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 3000)
})

const promise2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('bbb')
  }, 2000)
})

MyPromise.race([promsie1, promise2]).then(res => {
  console.log(res) // 成功执行 输出bbb
})

实现all

  1. 接收promise数组参数
  2. 当所有promise状态都变成fulfilled返回所有状态和对应的值
  3. 如果有一个promise的状态变成rejected,只返回这个错误的promise
 static all(promises) {
    const data = []
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(
          res => {
            data.push({ statu: 'fulfilled', value: res })
            if (data.length === promises.length) {
              resolve(data)
            }
          },
          err => {
            resolve({ statu: 'rejected', value: err })
          }
        )
      })
    })
  }

测试一下

const promsie1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 3000)
})

const promise2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('bbb')
  }, 2000)
})

MyPromise.all([promsie1, promise2]).then(res => {
  console.log(res) // 符合
})

实现any

  1. 接收promise数组参数
  2. 当有一个有promise状态变为fulfilled,返回改状态和对应的值
  3. 如果promise的状态全部都是rejected,则抛出错误
static any(promises) {
    const data = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(
          res => {
            data.push({ statu: 'fulfilled', value: res })
            if (data.length === promises.length) {
              resolve(data)
            }
          },
          err => {
            data.push({ statu: 'rejected', value: err })
            index++
            if (index === promises.length) {
              resolve({ err: 'all promise is rejected' })
            }
            if (data.length === promises.length) {
              resolve(data)
            }
          }
        )
      })
    })
  }

测试一下

const promsie1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('aaa')
  }, 2000)
})

const promise2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('bbb')
  }, 1000)
})

MyPromise.any([promsie1, promise2]).then(res => {
  console.log(res) // 成功报错
})

实现allSettled

  1. 接收promise数组参数
  2. 将所有的promise状态和结果放到数组里返回
 static allSettled(promises) {
    const data = []
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(
          res => {
            data.push({ statu: 'fulfilled', value: res })
            if (data.length === promises.length) {
              resolve(data)
            }
          },
          err => {
            data.push({ statu: 'rejected', value: res })
            if (data.length === promises.length) {
              resolve(data)
            }
          }
        )
      })
    })
  }

测试一下

const promsie1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('aaa')
  }, 2000)
})

const promise2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('bbb')
  }, 1000)
})

MyPromise.allSettled([promsie1, promise2]).then(res => {
  console.log(res)  // 成功返回
})

此篇总结于coderwhy老师的js高级课程。希望你看完这篇文章,对js函数回调掌握恐怖如斯。