理解promise基本原理

331 阅读7分钟

前言:promise用了不少,但是一直没有深究它是怎么实现的,今天把自己的理解梳理一下

promise也是一个类,我们先class一个类,再导出一下。promise有一个状态,默认是pending,并且是可以改变的,要先定义下

// promise.js
class Promise {
    // 构造函数
    constructor(){
        // promise的状态
        this.status = 'pending'
        
    }
}
module.exprots = Promise

使用的话,new一个就好了,不过这里要先导入我们自己写的promise

// base.js
const Promise = require('./promise')
// new Promise并传入一个函数作为参数,这个函数有两个返回值,一个是成功函数,一个是失败失败函数
let promise = new Promise((resolve,reject)=>{
    
})

回到promise.js,在构造函数的参数中加上传入的这个函数,并且在里面调用且传出成功态和失败态函数

// promise.js
class Promise {
    // 构造函数
    constructor(fn){
        // promise的状态
        this.status = 'pending'
        // 定义成功态函数
        let resolve = (succ)=>{
            
        }
        // 定义失败态函数
        let reject = (err)=>{
            
        }
        fn(resolve,reject)
    }
}
module.exprots = Promise

我们再在构造函数里新增两个变量分别存储调用resolve传进来的值和调用reject传进来的值。promise默认的状态是pending,我们在调用resolve和reject时都会改变它的状态,并且只有在promise状态为pending时才可以修改它的状态,还有特殊情况就是当报错时,会直接调用失败方法reject。

// promise.js
class Promise {
    // 构造函数
    constructor(fn){
        this.status = 'pending' // promise的状态
        this.resolveData = undefined // 存储调用resolve传进来的值
        this.rejectData = undefined //存储调用reject传进来的值
        let resolve = (succData)=>{
            if(this.status === 'pending'){
                this.status = 'resolve'
                this.resolveData = succData
            }
        }
        let reject = (errData)=>{
            if(this.status === 'pending'){
                this.status = 'reject'
                this.rejectData = errData
            }
        }
        //考虑到报错直接reject,这里用try catch处理下
        try{
            fn(resolve,reject)
        }catch (e){
            reject(e, '报错啦')
        }
    }
}
module.exprots = Promise

基本上按上面的,new Promise就可以使用最基础的功能了

// base.js
const Promise = require('./promise')
// new Promise并传入一个函数作为参数,这个函数有两个返回值,一个是成功函数,一个是失败失败函数
let promise = new Promise((resolve,reject)=>{
    //先写我们的业务代码
    
    //再调用成功函数或失败函数跳出这个函数
    resolve('成功啦')
    //reject('失败啦')
})

我们再promise加上then()方法,因为所有的用户都可以使用promise的then()方法,所以不能写在构造函数里面

// promise.js
class Promise {
    // 构造函数
    constructor(fn){
        this.status = 'pending' // promise的状态
        this.resolveData = undefined // 存储调用resolve传进来的值
        this.rejectData = undefined //存储调用reject传进来的值
        let resolve = (succData)=>{
            if(this.status === 'pending'){
                this.status = 'resolve'
                this.resolveData = succData
            }
        }
        let reject = (errData)=>{
            if(this.status === 'pending'){
                this.status = 'reject'
                this.rejectData = errData
            }
        }
        //考虑到报错直接reject,这里用try catch处理下
        try{
            fn(resolve,reject)
        }catch (e){
            reject(e, '报错啦')
        }
    }
    //then方法同样接受两个函数,成功函数和失败函数
    then(succFn,errFn){
        //这里的执行逻辑是根据new Promise结束的状态判断的
        if(this.status === 'resolve'){
            //调用方法的同时,要把new Promise的之前存的值传给它
            succFn(this.resolveData)
        }
        if(this.status === 'resolve'){
            //调用方法的同时,要把new Promise的之前存的值传给它
            errFn(this.rejectData)
        }
    }
}
module.exprots = Promise

按上面的,new Promise的then()方法最基本的一个使用也可以了,下面我们考虑特殊情况,如果在new Promise里面加了定时器之类的异步逻辑,就有可能造成定时器里面的resolve()和reject()方法不会执行,因为我们写的promise都是同步任务,会先执行,等定时器时间到了再执行resolve()或reject()时,状态就已经丢失了

// base.js
const Promise = require('./promise')
// new Promise并传入一个函数作为参数,这个函数有两个返回值,一个是成功函数,一个是失败失败函数
let promise = new Promise((resolve,reject)=>{
    console.log('这里立即执行')
    setTimeout(()=>{
        resolve('3秒后再执行成功回调')
    },3000)
})
promise.then((successData) => {
  console.log(successData, 'then成功回调');
}, (errData) => {
  console.log(errData, 'then失败回调');
})

上面这样加了定时器的话,console.log(successData, 'then成功回调')就永远都不会打印了,为了解决上面的问题,我们需要给promise把成功回调和失败回调都定义一个数组,在then()方法调用时只要状态为pending都存起来。这样在后面异步任务定时器执行完之后调用resolve或reject方法的时候,再循环这个数组并调用里面的方法。

// promise.js
class Promise {
    // 构造函数
    constructor(fn){
        this.status = 'pending' // promise的状态
        this.resolveData = undefined // 存储调用resolve传进来的值
        this.rejectData = undefined //存储调用reject传进来的值
        this.onSuccessCallback = [] // 定义一个数组存放成功回调的函数
        this.onErrCallback = [] // 定义一个数组存放失败回调的函数
        let resolve = (succData)=>{
            if(this.status === 'pending'){
                this.status = 'resolve'
                this.resolveData = succData
                this.onSuccessCallback.forEach((item) => {
                  item()
                })
            }
        }
        let reject = (errData)=>{
            if(this.status === 'pending'){
                this.status = 'reject'
                this.rejectData = errData
                this.onErrCallback.forEach((item) => {
                  item()
                })
            }
        }
        //考虑到报错直接reject,这里用try catch处理下
        try{
            fn(resolve,reject)
        }catch (e){
            reject(e, '报错啦')
        }
    }
    //then方法同样接受两个函数,成功函数和失败函数
    then(succFn,errFn){
        //这里的执行逻辑是根据new Promise结束的状态判断的
        if(this.status === 'resolve'){
            //调用方法的同时,要把new Promise的之前存的值传给它
            succFn(this.resolveData)
        }
        if(this.status === 'resolve'){
            //调用方法的同时,要把new Promise的之前存的值传给它
            errFn(this.rejectData)
        }
        //这里一定要是pending状态才存起来
         if (this.status === 'pending') {
            //传进去的参数要用一个函数包住,不然传进去的就是值而不是方法
           this.onSuccessCallback.push(() => { ()=>{success(this.resolveValue) }})
           this.onErrCallback.push(() => { ()=>{err(this.rejectValue) }})
         }
    }
}
module.exprots = Promise

按上面的代码,就能解决定时器这种异步问题啦,最基础的promise功能也基本上达到了。

下面再看一下promise的链式调用

// promise.js

//定义一个方法判断x是否是一个promise
const resolvePromise = (promise2, x, resolve, reject) => {
  //判断 可以你的promise和别的promise要混用
  //可能不同的promise库之间要相互调用
  if (promise2 === x) {
    //x如果和promise2是同一个的话x 永远不能成功或者失败,所以就卡死了,我们需要直接报错即可
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  //判断x的状态,到底是不是promise
  if ((typeof x === 'object' && x !== null) || x === 'function') {
    //x 必须是一个对象或者是函数
    let called = false // 增加一个变量,保证不能多次调成功或调失败
    try {
      let then = x.then // 取出zhen方法,这个then方法是采用defineProperty来定义的
      if (typeof then === 'function') {
        // 判断then是不是一个函数,如果then不是一个函数,说明不是promise
        // 只能认准它是一个promise了
        then.call(x, (y) => {// 如果x是一个promise  就采用promise的返回结果
          if (called) return //让一次成功之后就不能再走了
          called = true
          // 这里防止y又是promise,递归一下
          resolvePromise(y)
        }, (r) => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        // 如果then不是函数,是普通值,直接触发promise2的成功逻辑
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e) // 取then失败了,直接触发promise2的失败逻辑
    }
  } else {
    // 否则直接成功即可
    resolve(x)
  }
}
class Promise {
  // 构造函数
  constructor(fn) {
    this.status = 'pending' // promise的状态
    this.resolveData = undefined // 存储调用resolve传进来的值
    this.rejectData = undefined //存储调用reject传进来的值
    this.onSuccessCallback = [] // 定义一个数组存放成功回调的函数
    this.onErrCallback = [] // 定义一个数组存放失败回调的函数
    let resolve = (succData) => {
      if (this.status === 'pending') {
        this.status = 'resolve'
        this.resolveData = succData
        this.onSuccessCallback.forEach((item) => {
          item()
        })
      }
    }
    let reject = (errData) => {
      if (this.status === 'pending') {
        this.status = 'reject'
        this.rejectData = errData
        this.onErrCallback.forEach((item) => {
          item()
        })
      }
    }
    //try catch 只能捕获同步异常,定时器内的就捕获不到了,所以定时器内的异常要单独处理
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e, '报错啦')
    }
  }
  //then方法同样接受两个函数,成功函数和失败函数
  then (succFn, errFn) {
    // 增加可选参数处理
    succFn = typeof succFn === 'function' ? succFn : val => val
    errFn = typeof errFn === 'function' ? errFn : err => {
      throw err
    }

    //为了能实现链式调用这里我们以递归的方式定义一个promise
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === 'resolve') {
        //定义一个异步任务定时器,防止把promise2传出的时候,promise会提示未定义
        setTimeout(() => {
          //异步任务的异常在构造函数的try catch中捕获不到,这里要单独处理
          try {
            //将每一个值的都存起来给promise使用
            let x = succFn(this.resolveData)
            //在类的外面定义一个roselvePromise方法,并且把promise2的resolve把值传出去
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e, '报错啦')
          }
        }, 0)
      }
      if (this.status === 'resolve') {
        setTimeout(() => {
          try {
            //将每一个值的都存起来给promise使用
            let x = errFn(this.rejectData)
            //在类的外面定义一个roselvePromise方法,并且把promise2的resolve把值传出去
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e, '报错啦')
          }
        }, 0)
      }
      if (this.status === 'pending') {
        this.onSuccessCallback.push(() => {
          () => {
            setTimeout(() => {
              try {
                //将每一个值的都存起来给promise使用
                let x = success(this.resolveValue)
                //在类的外面定义一个roselvePromise方法,把promise2的resolve把值传出去
                resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e, '报错啦')
              }
            }, 0)
          }
        })
        this.onErrCallback.push(() => {
          () => {
            setTimeout(() => {
              try {
                //将每一个值的都存起来给promise使用
                let x = err(this.rejectValue)
                //在类的外面定义一个roselvePromise方法,把promise2的resolve把值传出去
                resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e, '报错啦')
              }
            }, 0)
          }
        })
      }
    })
    // 在执行完成后将promise2返回
    return promise2
  }
}
module.exprots = Promise

根据上面的代码,基本上一个完整的promise方法就实现了