美文网首页
“解剖” Promise

“解剖” Promise

作者: 火锅伯南克 | 来源:发表于2020-06-24 14:21 被阅读0次

    需要具备基础:

    [
       ' 同步和异步 ',
       ' 微任务与宏任务 ',
       ' 递归调用 ',
       ' 链式调用 ',
       ' 回调函数 ',
       ' 发布订阅 ',
       ' 描述符get ',
       ' 错误类型 ',
       ' try捕获 ',
       ' ES6 '
    ]
    

    必备的基础知识是要熟练掌握的。
    其次,得先明白Promise怎么用,才能知道怎么写。


    MDN-Promises

    本源码遵守的的是Promises/A+规范,另外还有ECMA-Promise规范,但实际上两者差别不大,如果你想写JS解释器那就只能看ECMA标准的Promise了😏。

    直接上代码了,真是没啥可说的,该注释的都注释了。

    (function(){
      const PENDING = Symbol('PENDING'); //等待状态
      const FULFILLED = Symbol('FULFILLED'); //成功状态
      const REJECTED = Symbol('REJECTED'); //失败状态
      const citeError = 'Chaining cycle detected for promise #<Promise>'
      const allError = 'is not a Array'
      const resolvePromise = (promise2, x, resolve, reject) => {
        //called变量是为了防止其他Promise库中没有对成功失败状态进行固定,
        //导致
        let called;
        //此处ES6得Promise规范是没有这个报错的
        //可以不判断,自己看情况
        if (promise2 === x) {
          return reject(new TypeError(citeError))
        }
        //在这里没有使用 x instanceof Promise 是因为要兼容其他的Promise库
        //其他人编写的Promise类库那就肯定不会和我们自己的Promise类有什么关联了啊
        if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
          try {
            //防止x的then属性使用get定义,get本身也是一个方法,执行过程可能报错
            let then = x.then
            if (typeof then !== 'function') {
              //防止x的then属性使用get定义,get本身也是一个方法,执行过程可能报错
              // 第二次执行可能报错,所以使用call来执行保险
              then.call(x, y => {
                if (called) return; //确保状态固定
                called = true;
                //递归调用,确保最终拿到非Promise类型值
                resolvePromise(promise2, y, resolve, reject)
              }, r => {
                if (called) return;//确保状态固定
                called = true;
                //错误直接输出,不必过滤到基础数据值
                reject(r)
              })
            } else {
              //如果then属性值不是一个函数,则证明x不是一个Promise类
              resolve(x)
            }
          } catch (err) {
            //x.then过程一旦报错,直接输出reject
            if (called) return;//确保状态固定
            called = true;
            reject(err)
          }
        } else {
          //基础数据类型直接输出resolve
          resolve(x)
        }
      }
    
      class Promise {
        constructor(executor) {
          if(typeof executor !== 'function'){
            throw new TypeError('Promise resolver #<Object> is not a function')
          }
          this.status = PENDING; //实例的状态,默认为等待中
          this.value = undefined; //成功状态的值
          this.reason = undefined; //失败状态的值
          this.onFulfilledCallbacks = []; //成功回调订阅容器
          this.onRejectedCallback = []; //失败回调订阅容器
          //成功的触发器
          this.resolve = (value) => {
            //如果成功回调的value为promise实例,则把自己的成功失败的触发器
            //传递给value的then,如果value的then依然是promise实例。则会循环往复
            //直到拿到非promise实例的值
            //注意不要把此处的处理和onFulfilled和onRejected的return值概念相混淆。
            //这个实例最终执行时还会进入异步,所以在此处不需要异步执行
            if (value instanceof Promise) {
              return value.then(this.resolve, this.reject)
            }
            //触发器一定要异步调用,这是规定
            setTimeout(()=>{
              //只有在等在状态才可以执行,因为状态一定确定,就不能在修改以及执行
              if (this.status === PENDING) {
                this.value = value //更新值
                this.status = FULFILLED //更新状态
                //把订阅容器预存的onFulfilled函数依次执行
                for (let fn of this.onFulfilledCallbacks) {
                  fn()
                }
              }
            })
          }
          //失败的触发器
          this.reject = (reason) => {
            //触发器一定要异步调用,这是规定
            setTimeout(()=>{
              if (this.status === PENDING) {
                this.reason = reason
                this.status = REJECTED
                //这段代码处理了一个问题,就是当Promise实例没有指定失败回调,
                //但是却执行了reject,导致错误信息丢失,这是不应该的,我们需要
                //抛出一个错误提示,但最好也不要阻碍程序运行。
                if (this.onRejectedCallback.length === 0) {
                  console.error(reason)
                }
                for (let fn of this.onRejectedCallback) {
                  fn()
                }
              }
            })
          }
          //如果传入构造器的函数执行出现错误,需要使用reject的方式输出
          try {
            executor(this.resolve, this.reject)
          } catch (err) {
            this.reject(err)
          }
    
        }
        //添加一个可以得到类型的属性,添加这条属性可以使用
        // Object.prototype.toString.call(实例) = '[object Promise]'
        get [Symbol.toStringTag]() {return "Promise"}
    
        //通过then方法添加成功和失败的回调
        then(onFulfilled, onRejected) {
          //有了这两个方法,可以实现then没参数的情况下无限传递
          //实例.then().then().then()....
          if (typeof onFulfilled !== 'function') {
            onFulfilled = val => val
          }
          if (typeof onRejected !== 'function') {
            onRejected = err => { throw err }
          }
          //then方法必须返回一个新的Promise实例,已实现链式调用
          //Jquery的链式调用通过 renturn this,注意领悟精神
          let _resolve, _reject;
          const promise2 = new Promise((resolve, reject) => {
            //注意,这部分还是同步执行的,吧resolve,reject外置,作用是
            //可以把外部的实例拿到值时,通过_resolve和_reject触发器传入给promise2
            _resolve = resolve
            _reject = reject
          })
          //如果已成功,不必加入订阅容器,直接执行
          if (this.status === FULFILLED) {
            try {
              //x 为 onFulfilled()的返回值
              //注意此处的返回值x有可能是Promise实例的,必须处理成
              //非Promise实例的值,由resolvePromise函数处理
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, _resolve, _reject)
            } catch (err) {
              //如果onFulfilled的函数抛出异常,直接把错误对象返回
              _reject(err)
            }
          }
          //如果已失败,不必加入订阅容器,直接执行
          if (this.status === REJECTED) {
            try {
              // 同上
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, _resolve, _reject)
            } catch (err) {
              //同上
              _reject(err)
            }
          }
          //如果是等待中,需要加入订阅容器,等待触发器触发时再来调用
          if (this.status === PENDING) {
            //和上面意思一样,只不过这部分被预存了,联想发布订阅
            this.onFulfilledCallbacks.push(() => {
              try {
                let x = onFulfilled(this.value)
                resolvePromise(promise2, x, _resolve, _reject)
              } catch (err) {
                _reject(err)
              }
            })
            this.onRejectedCallback.push(() => {
              try {
                let x = onRejected(this.reason)
                resolvePromise(promise2, x, _resolve, _reject)
              } catch (err) {
                _reject(err)
              }
            })
          }
          return promise2
        }
        catch(errCallback) {
          return this.then(null, errCallback)
        }
        finally(callback) {
          return this.then(
            value => Promise.resolve(callback()).then(() => value),
            //这块为什么用Promise.resolve千万别迷糊,使用Promise.reject
            //也是完全可以的,只不过在后面的你需要这么写
            //.then(null, () => { throw reason },少传个参数显得高档。。
            reason => Promise.resolve(callback()).then(() => { throw reason })
          )
        }
        static resolve(value) {
          //这个判断可以不写的,但是写了可以调高一点点性能
          if(value instanceof Promise) return value
          return new Promise(resolve => {
            resolve(value)
          })
        }
        static reject(reason) {
          //reason无论什么数据类型,都要直接使用reject输出
          //不能像Promise.resolve方法那样有个优化性能的判断
          //因为那样有可能触发onFulfilled,那就违背原则了
          return new Promise((resolve, reject) => {
            reject(reason)
          })
        }
        static all(promises) {
          //这里注意,我是图方便限定格式为数组,实际ES6的参数设定是必须是
          //一个可迭代对象
          if (!(promises instanceof Array)) {
            throw new Error(`${typeof promises} ${promises} ${allError}`)
          }
          //数组为空,直接返回一个Promise.resolve
          if (promises.length === 0) {
            return Promise.resolve(promises)
          }
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              let arr = [];
              let i = 0;
              let processData = (index, data) => {
                arr[index] = data;
                if (++i === promises.length) {
                  resolve(arr)
                }
              }
              promises.forEach((item, i) => {
                if (item instanceof Promise) {
                  item.then(data => {
                    processData(i, data)
                  }, reject)
                } else {
                  processData(i, item)
                }
              })
            })
          })
        }
        static race (promises) {
          //如果传的迭代是空的,则返回的 promise 将永远等待。
          //这个特性可以用于停止Promise链。
          if(promises === undefined){
            return new Promise(()=>{})
          }
          if (!(promises instanceof Array)) {
            throw new Error(`${typeof promises} ${promises} ${allError}`)
          }
          if (promises.length === 0) {
            return Promise.resolve(promises)
          }
          return new Promise((resolve, reject) => {
            for (let p of promises) {
              if(!(p instanceof Promise)){
                p = Promise.resolve(p)
              }
              // 只要有一个实例率先改变状态,Promise的状态就固定
              p.then(res => {
                resolve(res)
              }, err => {
                reject(err)
              })
            }
          })
        }
      }
      try{
        module.exports = Promise
      }catch(e){
        window.Promise = Promise
      }
    })()
    

    相关文章

      网友评论

          本文标题:“解剖” Promise

          本文链接:https://www.haomeiwen.com/subject/sfzlfktx.html