针要学前端 | JavaScript深度挖掘之手写Promise

538 阅读11分钟

大家好,我是指针。冬天到了,人也变懒了,为了让自己动起来,我报名参加了拉钩教育的大前端高薪训练营。学习需要总结,需要分享,需要鞭策,于是便有了《针爱学前端》这一系列,希望大家看完能够有收获。如果文章中有不对的地方,希望能批评指正,不吝赐教!!!

0.亲身经历的呼吁

大家用谷歌,用谷歌,用谷歌!!!360极速浏览器,class属性写在构造函数外面会报错,耽误了我好久,没想过是浏览器的问题,最后经过老师的提醒才发现,太坑人了!!!

1.例行叨叨叨

新年将近,我送给大家一副对联吧。

上联:金三银四,手写,算法,面试造火箭

下联:朝九晚五,框架,类库,工作拧螺丝

横批:我爱大厂

不开玩笑了,这些都是我自愿的,我自愿手写Promise,完全是出于真爱,我要是撒谎,我以后买键盘没有cv键/(ㄒoㄒ)/~~

2.如何手写Promise

  • 首先,先把我上一期的文章阅读全文并背诵
  • 其次,明白Promise有哪些功能
  • 最后,干就完事了嗷XDM,奥里给!

3.开始手写

1.Promise首先是个类,因为它可以实例化,我们使用es6提供的class来模拟它,当然构造函数也行,但是写的太烦了,算了算了

2.Promise接受一个立即执行的函数,这个函数又接受两个参数,一个resolve,一个reject

3.Promise包含三种状态,初始状态等待(pending),成功状态(fulFilled),失败状态(rejected)

4.Promise的状态由resolve和reject改变

第一步,咱们就先完成上面的四点吧

// 用常量定义三个状态,避免单词写错的小尴尬事件
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject)
  }

  status = PENDING

  resolve = () => {
    this.status = FULFILLED
  }
  reject = () => {
    this.status = REJECTED
  }
}

5.Promise的状态一经改变,就不能再变化,状态已经锁定了

6.Promise的then方法接受两个函数作为参数,一个成功回调函数,一个失败回调函数,两个函数也接受一个参数,分别表示成功的结果和失败的原因,这两个值由resolve和reject分别传递过来

// 声明了两个参数value和reason来保存成功和失败的结果
// 方便then方法的成功与失败回调使用
// 而Promise的状态改变之后便锁定也很容易实现,在改变status之前判断status是否等于pending
// 如果status !== pending,则代表status已经发生了改变,直接return

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject)
  }

  status = PENDING
  // 成功的值
  value = undefined
  // 失败的原因
  reason = undefined

  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
  }
  then = (successCallback, failCallback) => {
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else {
      failCallback(this.reason)
    }
  }
}

let p = new MyPromise((res, rej) => {
  console.log(111);
  res("success")
  rej("fail")
})

// 只会执行成功回调
p.then(value => {
  console.log(value);
}, err => {
  console.log(err);
})

7.Promise是用来处理异步的,那么我们试着加入异步的逻辑,怎么加?如果执行到then时,status === pending,将then里的方法储存起来,在resolve或reject的最后执行就行啦,类似于回调函数,类似于哈,但不是,你就这么想就行了

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject)
  }

  status = PENDING
  // 成功的值
  value = undefined
  // 失败的原因
  reason = undefined
  // 新增了成功回调
  successCallback = undefined
  // 新增了失败回调
  failCallback = undefined

  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    
    // 执行异步逻辑
    this.successCallback && this.successCallback(this.value)
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason

    // 执行异步逻辑
    this.failCallback && this.failCallback(this.reason)
  }
  then = (successCallback, failCallback) => {
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
      // 此时status为pending,说明处于异步情况,我们需要将成功回调和失败回调存储起来
      // 在resolve()或reject()最后的地方执行
      this.successCallback = successCallback
      this.failCallback = failCallback
    }
  }
}

// 首先then执行了,但是因为res在两秒后才会执行,所以then里的status===pending
// 所以then里的回调不会执行,而是先储存起来
// 2秒后,res执行,成功回调在res的最后执行
// 这就是Promise通过then执行异步逻辑的原因,将同步代码保存起来,放在异步代码的最后执行
let p = new MyPromise((res, rej) => {
  setTimeout(() => {
    res("success")
  }, 2000)
})

p.then(value => {
  console.log(value);
}, err => {
  console.log(err);
})

8.Promise的then方法可以多次调用,每次调用的方法可能都不相同,所以我们需要将所有的成功回调与失败回调保存起来全部保存起来,在status改变之后,遍历调用

当然同步代码不影响,因为直接执行了,异步代码因为是保存着的,所以现在我们要用一个数组给他包起来了

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject)
  }

  status = PENDING
  // 成功的值
  value = undefined
  // 失败的原因
  reason = undefined
  // 新增成功回调集合
  successCallbackList = []
  // 新增失败回调集合
  failCallbackList = []

  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    
    // 执行异步逻辑
    // this.successCallback && this.successCallback(this.value)
    // 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
    while (this.successCallbackList.length) this.successCallbackList.shift()(this.value) 
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason

    // 执行异步逻辑
    // this.failCallback && this.failCallback(this.reason)
    while (this.failCallbackList.length) this.failCallbackList.shift()(this.value) 
  }
  then = (successCallback, failCallback) => {
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
      // 将回调函数保存在数组里,遍历执行
      this.successCallbackList.push(successCallback)
      this.failCallbackList.push(failCallback)
    }
  }
}

let p = new MyPromise((res, rej) => {
  setTimeout(() => {
    res("success")
  }, 2000)
})

p.then(value => {
  console.log(value);
}, err => {
  console.log(err);
})

p.then(value => {
  console.log(222);
}, err => {
  console.log(err);
})

p.then(value => {
  console.log(333);
}, err => {
  console.log(err);
})

9.Promise的链式调用,是解决回调地狱的关键。你知道为啥能链式调用吗?现在我们知道Promise对象可以执行.then,所以每次.then过后就返回一个新的Promise就行了,这点我是在函数式编程的函子那里得到的思路,没看过的,看我以前的文章函数式编程吧

10..then(success, fail)返回一个Promise,定为promise2,该Promise的状态由success或者fail的返回值value来决定。这里value分为两种,一种是值返回,会触发promise2的res(value);另一种value是一个Promise对象,定为promise3,我们就要执行该promise3.then(),如果promise3的成功或失败回调还是一个Promise对象,则再次执行,直到返回的是一个值

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject)
  }

  status = PENDING
  // 成功的值
  value = undefined
  // 失败的原因
  reason = undefined
  // 成功回调集合
  successCallbackList = []
  // 失败回调集合
  failCallbackList = []

  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    
    // 执行异步逻辑
    // 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
    while (this.successCallbackList.length) this.successCallbackList.shift()(this.value)
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason

    // 执行异步逻辑
    while (this.failCallbackList.length) this.failCallbackList.shift()(this.reason) 
  }
  then = (successCallback, failCallback) => {
    // 这里实例化了一个promise2,并且返回它,以供后面链式调用
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        let result = successCallback(this.value)
        // 判断result的类型
        judgeResult(result, resolve, reject)
      } else if (this.status === REJECTED) {
        let result = failCallback(this.reason)
        // 判断result的类型
        judgeResult(result, resolve, reject)
      } else {
        this.successCallbackList.push(() => {
          let result = successCallback(this.value)
          // 判断result的类型
          judgeResult(result, resolve, reject)
        })
        this.failCallbackList.push(() => {
          let result = failCallback(this.reason)
          // 判断result的类型
          judgeResult(result, resolve, reject)
        })
      }
    })
    
    return promise2
  }
}

/**
 * 判断result,如果是个值,那就直接resolve
 * 如果是个Promise,那需要执行.then(),根据.then的结果返回
 */
function judgeResult(result, resolve, reject) {
  if (result instanceof MyPromise) {
    result.then(value => resolve(value), error => reject(error))
  } else {
    resolve(result)
  }
}

p.then(value => {
  console.log(value);
  return "p1--success"
}).then(value => {
  console.log(value);
  return new MyPromise((res, rej) => {
    rej("p2--fail")
  })
}).then(undefined, err => {
  console.log(err);
  return new MyPromise((res, rej) => {
    setTimeout(()=> {
      res("p3--async")
    }, 1000)
  })
}).then(value => {
  console.log(value);
})

11.then返回的如果是个Promise,那么这个返回不能是自己,否则就死循环了,这里也要处理一下

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject)
  }

  status = PENDING
  // 成功的值
  value = undefined
  // 失败的原因
  reason = undefined
  // 成功回调集合
  successCallbackList = []
  // 失败回调集合
  failCallbackList = []

  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    
    // 执行异步逻辑
    // 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
    while (this.successCallbackList.length) this.successCallbackList.shift()()
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason

    // 执行异步逻辑
    while (this.failCallbackList.length) this.failCallbackList.shift()() 
  }
  then = (successCallback, failCallback) => {
    // 这里实例化了一个promise2,并且返回它,以供后面链式调用
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
        setTimeout(() => {
          let result = successCallback(this.value)
          // 判断result的类型
          judgeResult(promise2, result, resolve, reject)
        }, 0)
      } else if (this.status === REJECTED) {
        // 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
        setTimeout(() => {
          let result = failCallback(this.reason)
          // 判断result的类型
          judgeResult(promise2, result, resolve, reject)
        }, 0)
      } else {
        this.successCallbackList.push(() => {
          // 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
          setTimeout(() => {
            let result = successCallback(this.value)
            // 判断result的类型
            judgeResult(promise2, result, resolve, reject)
          }, 0)
        })
        this.failCallbackList.push(() => {
          // 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
          setTimeout(() => {
            let result = failCallback(this.reason)
            // 判断result的类型
            judgeResult(promise2, result, resolve, reject)
          }, 0)
        })
      }
    })
    
    return promise2
  }
}

/**
 * 判断result,如果是个值,那就直接resolve
 * 如果是个Promise,那需要执行.then(),根据.then的结果返回
 */
function judgeResult(promise2, result, resolve, reject) {
  if(promise2 === result) {
    return reject("别自己返回自己啊喂!!!")
  }
  if (result instanceof MyPromise) {
    result.then(value => resolve(value), error => reject(error))
  } else {
    resolve(result)
  }
}

p.then(value => {
  console.log(value);
  return "p1--success"
}).then(value => {
  console.log(value);
  return new MyPromise((res, rej) => {
    rej("p2--fail")
  })
}).then(undefined, err => {
  console.log(err);
  return new MyPromise((res, rej) => {
    setTimeout(()=> {
      res("p3--async")
    }, 1000)
  })
}).then(value => {
  console.log(value);
})

// 测试不能返回自己
let p2 = p.then((value) => {
  console.log(value);
  return p2
})

p2.then((value)=> {
  console.log(value)
}, (error) => {
  console.log(error)
})

12.then方法的参数不是一个函数时,会自动转换成一个value => value的方法

13.Promise中,每个地方产生的错,最终都会被捕获,所以现在把该捕获错误的地方都捕一捕

// 如何捕获失败,用try/catch,在哪里捕获?在你认为会错的地方😀
class MyPromise {
  constructor (executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  status = PENDING
  // 成功的值
  value = undefined
  // 失败的原因
  reason = undefined
  // 成功回调集合
  successCallbackList = []
  // 失败回调集合
  failCallbackList = []

  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    
    // 执行异步逻辑
    // 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
    while (this.successCallbackList.length) this.successCallbackList.shift()()
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason

    // 执行异步逻辑
    while (this.failCallbackList.length) this.failCallbackList.shift()() 
  }
  then = (successCallback, failCallback) => {
    // 判断successCallback和failCallback是不是函数,如果不是,转化成一个函数
    successCallback = typeof successCallback === 'function' ? successCallback : value => value
    failCallback = typeof failCallback === 'function' ? failCallback : value => value

    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        try {
          setTimeout(() => {
            let result = successCallback(this.value)
            // 判断result的类型
            judgeResult(result, resolve, reject)
          }, 0)
        } catch (error) {
          reject(error)
        }
      } else if (this.status === REJECTED) {
        try {
          setTimeout(() => {
            let result = failCallback(this.reason)
            // 判断result的类型
            judgeResult(result, resolve, reject)
          }, 0)
        } catch (error) {
          reject(error)
        }
      } else {
        this.successCallbackList.push(() => {
          try {
            setTimeout(() => {
              let result = successCallback(this.value)
              // 判断result的类型
              judgeResult(result, resolve, reject)
            }, 0)
          } catch (error) {
            reject(error)
          }
        })
        this.failCallbackList.push(() => {
          try {
            setTimeout(() => {
              let result = failCallback(this.reason)
              // 判断result的类型
              judgeResult(result, resolve, reject)
            }, 0)
          } catch (error) {
            reject(error)
          }
        })
      }
    })
    
    return promise2
  }
}

/**
 * 判断result,如果是个值,那就直接resolve
 * 如果是个Promise,那需要执行.then(),根据.then的结果返回
 */
function judgeResult(promise2, result, resolve, reject) {
  throw(new Error("judgeResult error"))
  if(promise2 === result) {
    return reject("别自己返回自己啊喂!!!")
  }
  if (result instanceof MyPromise) {
    result.then(value => resolve(value), error => reject(error))
  } else {
    resolve(result)
  }
}

let p = new MyPromise((res, rej) => {
  res("success")
})

p.then().then("b").then(value => console.log(value))

14.Promise包含几个重要的静态方法,就是我在上一章列出的几个,现在我们实现一下

// Promise.all 故名思意,就是等全部,全部成功之后,返回一个Promise对象,成功回调全部返回的数组
// 如果有失败,则会返回一个Promise对象,失败回调第一个失败的返回
// 之前采用push,经过好心人提醒,异步会导致返回的结果与原来的Promise对象数组的顺序不一致
// 改为普通的for循环,使用一个count计数,count == len时,便res(arr)
static all(array) {
  try {
    if (!(array instanceof Array)) throw(new Error("请传入一个数组好吗"))
    if (array.length == 0) throw(new Error("请不要传入一个空数组好吗"))
    let arr = []
    let len = array.length
    let count = 0;
    function addAndJudgeAll(index, value, res) {
      arr[index] = value
      count++
      if (count == len) res(arr)
    }
    return new MyPromise((res, rej) => {
      for(let i = 0; i < len; i++) {
        if (array[i] instanceof MyPromise) {
          array[i].then(value=> {
            addAndJudgeAll(i, value, res)
          }, error => {
            rej(error)
          })
        } else {
          addAndJudgeAll(i, array[i], res)
        }
      }
    })

  } catch (error) {
    console.log(error);
  }
}
// Promise.race故名思意,竞争,看谁先结束,返回的Promise就用谁的返回
static race(array) {
    try {
        if (!(array instanceof Array)) throw(new Error("请传入一个数组好吗"))
        if (array.length == 0) throw(new Error("请不要传入一个空数组好吗"))
        return new MyPromise((res, rej) => {
            for(let item of array) {
                if (item instanceof MyPromise) {
                    item.then(value=> {
                        res(value)
                    }, error => {
                        rej(error)
                    })
                } else {
                    res(item)
                }
            }

        })
    } catch (error) {
        console.log(error);
    }
}
// Promise.resolve 参数如果不是Promise对象,直接返回一个成功回调为参数的Promise对象
// 如果参数是Promise对象,那直接返回参数即可
static resolve(params) {
    try {
        if(params instanceof MyPromise) {
            return params
        } else {
            return new MyPromise(res => {
                res(params)
            })
        }
    } catch (error) {
        console.log(error);
    }
}

15.最后还有.catch和.finally方法

// .catch就是一个没有成功回调的then
static catch(failCallback) {
    return this.then(null, failCallback)
}
// .finally 
// 回调函数中不包含当前Promise的返回值,也就是callback不传递参数
// 但是会把当前Promise的返回值return出去,所以调用this.then,获取当前Promise的返回值
// 如果finally返回一个Promise,会执行,但是仍然返回当前Promise的返回值,而不是fianlly返回的Promise的返回值
static finally(callback) {
    this.then(value => {
        this.resolve(callback()).then(() => value)
    }, error => {
        this.resolve(callback()).then(() => {
            throw error
        })
    })
} 

p1.then(()=> {
  return "1111"
}).finally(value => {
  console.log(value);	// undefined
  return new MyPromise(res => {
    res("2222")
  })
}).then(value=> {
  console.log(value);	// "1111"
})

总结

终于写完了,写的过程又看了一遍,练了一遍。我自己看着觉得还是挺满意的,希望能够给大家一点帮助吧。一时看不懂,或者记不住不要紧,多练多写,可以按照我的提纲,一步步完善,循序渐进,总有一天,会全部掌握的,未来的大神们。

导航

针要学前端 | JavaScript深度挖掘之函数式编程

针要学前端 | JavaScript深度挖掘之异步编程

针要学前端 | JavaScript深度挖掘之手写Promise

针要学前端 | JavaScript深度挖掘之ECMAScript

参考

以上皆由拉钩教育大前端训练营提供材料😀