“解剖”-Promise

需要具备基础:

[
   ' 同步和异步 ',
   ' 微任务与宏任务 ',
   ' 递归调用 ',
   ' 链式调用 ',
   ' 回调函数 ',
   ' 发布订阅 ',
   ' 描述符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
  }
})()