手写一个promise, 更了解promise

导读

上一篇我们讲解了Promise的使用,今天我们来通过Promise/A+ 规范来手写一个MyPromise类,废话不多说,让我们开始把。

规范定义了些什么

Promise状态

    1. promise 有且只有一种状态(pending, fulfilled, rejected三种状态中的其中一种)
    1. promise在pending状态下面可以转换成fulfilled状态 或者 rejected状态
    1. fulfilled状态下,不能再转换成其他状态,有且必须有一个值value, 不能被改变
    1. rejected状态下,不能再转换成其他状态,有且必须有一个原因reason, 不能被改变

then方法

    1. then方法用来获取转成一种状态下的value或者reason
    1. 接受两个参数,onFulfilled, onRejected(两个都是函数)
    1. 如果onFulfilled, onRejected不是函数,则必须忽略
    1. 如果onFulfilled是一个函数
    • 必须在转换成状态fulfilled完成后调用,并且把value作为函数的第一个参数
    • 不能在fulfilled状态完成之前调用
    • 最多调用一次
    1. 如果onRejected是一个函数
    • 必须在转换成状态rejected完成后调用,并且把reason作为函数的第一个参数
    • 不能在rejected状态完成之前调用
    • 最多调用一次
    1. onFulfilled和onRejected都是函数调用,所以this指向全局global, 严格模式undefined
    1. then方法可以多次被调用
    • 当promise完成fulfilled状态时候,各个onFulfilled回调必须按照原始的then顺序来调用
    • 当promise拒绝rejected状态时候,各个onRejected回调必须按照原始的then顺序来调用
    1. then必须返回一个新的promise对象 promise2 = promise1.then(onFulfilled, onRejected);
    • 如果onFulfilled或者onRejected回调返回一个值x, 对x进行判断分析,请看分析then返回的x值
    • 如果onFulfilled或者onRejected 抛出一个异常,那么promise2必须是拒绝状态,并且异常信息作为拒绝的原因
    • 如果onFulfilled不是一个函数,并且promise1已经是fulfilled状态,那么promise2则必须是和promise1一样的值来完成状态promise
    • 如果onRejected不是一个函数,并且promise1已经是rejected状态,那么promise2则必须和promise1一样的原因来拒绝状态promise

分析then返回的x值(resolvePromise )

    1. 如果promise与x引用的是同一个对象,则抛出TypeError作为拒绝原因的promise
    1. 如果x是一个promise对象
    • 2.1 如果这个 x 在pending状态,则promise需要保持pending状态,直到x转换成fulfilled或者是rejected状态
    • 2.2 如果这个 x 已经是fulfilled状态, 使用相同的值完成fulfilled promise
    • 2.3 如果这个 x 已经是rejected状态, 使用相同的原因拒绝rejected promise
    1. 如果x是一个对象或者是函数类型
    • 3.1 将then 设置为x.then
    • 3.2 如果检测属性x.then 返回一个异常结果e, 则以e为原因reject promise
    • 3.3 如果then是一个函数,用x作为this调用它,第一个参数resolvePromise,第二个参数rejectPromise
      • 3.3.1 如果当resolvePromise的值为y时, 继续解析,运行resolvePromise
      • 3.3.2 如果当rejectPromise以一个理由r调用,则直接以r为原因reject promise
      • 3.3.3 如果同时调用了resolvePromise和rejectPromise,或者多次调用相同的参数,那么第一次调用优先,后续的调用将被忽略。
      • 3.3.4 如果调用then抛出异常e
        • 3.3.4.1 如果resolvePromise或者rejectPromise已经被调用,则忽略
        • 3.3.4.2 或者以e为原因reject promise
    • 3.4 如果then不是一个函数,用x完成(fulfill)promise
    1. 如果x不是一个对象或函数,用x完成(fulfill)promise

手写MyPromise

1. 基础版

// 定义三个状态常量
const PENDING = 'pending' // 待定状态
const FULFILLED = 'fulfilled' // 完成状态
const REJECTED = 'rejected' // 拒绝状态

class MyPromise {
  // 接受一个函数executor
  constructor(executor) {
    // 初始化待定状态
    this.status = PENDING
    // 初始化value
    this.value = undefined
    // 初始化原因reason
    this.reason = undefined

    // 定义完成时,执行函数
    let resolve = (value) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
      }
    }

    // 定义拒绝时,执行函数
    let reject = (reason) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
      }
    }

    // try/catch 捕捉异常,一点执行失败就reject promise
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  /**
   * then 方法接受两个参数
   * @param {成功回调} onFulfilled 
   * @param {失败回调} onRejected 
   */
  then (onFulfilled, onRejected) {
    // 当状态更新成完成,执行成功回调onFulfilled,并将value作为第一个参数
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    // 当状态更新成拒绝,执行失败回调onRejected,并将reason作为第一个参数
    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}
复制代码

我们来一起测试一下:

const p1 = new MyPromise((resolve, reject) => {
    resolve("ok");
}).then(
    (value) => {
      console.log("success", value);
    },
    (reason) => {
      console.log("faild", reason);
    }
);
复制代码

控制台输出:

'success ok'
复制代码

现在实现了一个基础版本,但是有一个问题就是executor中异步执行resolve或者reject,就会出现什么反应都没有的情况,请看下面:

const p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve("ok");
    }, 1000);
  }).then(
    (value) => {
      console.log("success", value);
    },
    (reason) => {
      console.log("faild", reason);
    }
  );
复制代码

因为在执行then方法的时候,状态还处在pending,不是完成状态,所以没有执行。在这里我们就应该在pending状态的时候将完成的回调和拒绝的回调分别收集起来,等到异步执行后,触发resolve或者reject, 然后依次执行回调 。 因为promise的回调是异步的(是微任务)暂且用setTimeout(宏任务)来实现下, 我们来优化一下:

// 定义三个状态常量
const PENDING = 'pending' // 待定状态
const FULFILLED = 'fulfilled' // 完成状态
const REJECTED = 'rejected' // 拒绝状态

class MyPromise {
  // 接受一个函数executor
  constructor(executor) {
    ...

    // 存放成功回调
    this.onFulfilledCallbacks = []
    // 存放失败回调
    this.onRejectedCallbacks = []

    // 定义完成时,执行函数
    let resolve = (value) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
        // 更新完成状态,依次执行成功回调
        this.onFulfilledCallbacks.forEach(fn=>fn())
      }
    }

    // 定义拒绝时,执行函数
    let reject = (reason) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        // 更新拒绝状态后,依次执行成功回调
        this.onRejectedCallbacks.forEach(fn=>fn())
      }
    }

    // try/catch 捕捉异常,一点执行失败就reject promise
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  /**
   * then 方法接受两个参数
   * @param {成功回调} onFulfilled 
   * @param {失败回调} onRejected 
   */
  then (onFulfilled, onRejected) {
    ...

    // 处于pending状态 收集回调
    if (this.status === PENDING) {
      // 收集成功回调
      this.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          onFulfilled(this.value)
        },0)
      })
      // 收集失败回调
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          onRejected(this.reason)
        },0)
      })
    }
  }
}
复制代码

2. then 方法的两个参数onFulfilled和onRejected

根据上述规范定义then方法中的参数,需要对onFulfilled和onRejected进行判断

 /**
   * then 方法接受两个参数
   * @param {成功回调} onFulfilled 
   * @param {失败回调} onRejected 
   */
then (onFulfilled, onRejected) {
    // 判断onFulfilled,onRejected
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    
    ....
 }
复制代码

3. then 方法的链式调用

then方法链式调用new Promise((resolve,reject)=>{}).then().then(),then会返回一个新的promise,规范中有这么说的:

如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,再加之我们拿到x值后还需要处理,所以总共传入4个参数。我们将这个解决过程起名resolvePromise,先将它抽象出来,随后将刚才考虑到的情况及别的情况在里面处理,onFulfilled 或者 onRejected也有可能会执行错误所以用try/catch捕捉异常,所以修改如下:

// 定义三个状态常量
const PENDING = "pending"; // 待定状态
const FULFILLED = "fulfilled"; // 完成状态
const REJECTED = "rejected"; // 拒绝状态

class MyPromise {
  
  ...
  
  /**
   * then 方法接受两个参数
   * @param {成功回调} onFulfilled
   * @param {失败回调} onRejected
   */
  then(onFulfilled, onRejected) {
    // 判断onFulfilled,onRejected
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    let promise2 = new MyPromise((resolve, reject) => {
      // 当状态更新成完成,执行成功回调onFulfilled,并将value作为第一个参数
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      // 当状态更新成拒绝,执行失败回调onRejected,并将reason作为第一个参数
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      // 处于pending状态 收集回调
      if (this.status === PENDING) {
        // 收集成功回调
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
        // 收集失败回调
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

复制代码

3.1 Promise 解决过程 resolvePromise 函数

function resolvePromise (promise2, x, resolve, reject) {
  // 1. 如果promise与x引用的是同一个对象,则抛出TypeError作为拒绝原因的promise
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }

  // 防止多次调用
  let called;

  // 如果x是一个对象(包含promise)或者是一个函数, 如果是null用x完成(fulfill)promise
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then
      // then是函数,默认x是promise
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return
          called = true
          // 继续判断y值是否是promise, 递归resolvePromise
          resolvePromise(promise2, y, resolve, reject)
        }, reason => {
          if (called) return
          called = true
          reject(reason)
        })
      } else {
        resolve(x)
      }
    } catch (error) {
      if (called) return
      called = true
      reject(error)
    }
  } else {
    resolve(x)
  }
}
复制代码

4. 测试MyPromise

1、npm 有一个promises-aplus-tests插件 npm i promises-aplus-tests -D

2、命令行 promises-aplus-tests [js文件名] 即可验证

新建testMyPromise.js

const MyPromise = require('./MyPromise')

// 用于promises-aplus-tests 测试定义的方法
MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });
  return result;
}

module.exports = MyPromise
复制代码

修改package.json

"scripts": {
    "test": "promises-aplus-tests testMyPromise"
  },
复制代码

执行命令

npm run test
复制代码

5. 原型方法catch 和 finally

5.1 catch

.catch() 其实只是没有给 onFulfilled 预留参数位置的 .then() 而已。同样也返回一个promise


  catch (onRejected) {
    return this.then(null, onRejected)
  }
复制代码

5.2 finally

无论结果是fulfilled或者是rejected,都会执行指定的回调函数, 同样也返回一个promise

finally (callback) {
    // 构造函数
    let p = this.constructor

    return p.then(value => {
      return p.resolve(callback()).then(()=>value)
    }, reason => {
      return p.resolve(callback()).then(()=>{throw reason})
    })
}
复制代码

6. 静态方法

6.1 resolve

静态方法 resolve返回一个解析过的Promise对象。如果传入的值是一个promise,就要递归解析,所以这里我们还要改一处代码就是 constructor 中的resolve方法,请看:

// 定义完成时,执行函数
let resolve = (value) => {
  // ======新增逻辑======
  // 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析
  if(value instanceof MyPromise){
    // 递归解析 
    return value.then(resolve,reject)
  }

};
复制代码

resolve方法

// 静态方法 Promise.resolve返回一个解析过的Promise对象。
static resolve (value) {
    return new MyPromise((resolve, reject) => {
      resolve(value)
    })
}
复制代码

6.2 reject()

静态方法reject, 返回一个带有拒绝原因的Promise对象。

static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
}
复制代码

6.3 all()

all() 方法接收一个promise的iterable类型(Array, Map, Set),只返回一个promise实例,onFulfilled回调的是所有promise实例的完成状态结果数组作为参数。onRejected回调的结果是只要其中一个拒绝就返回当前拒绝的原因作为参数

// 静态方法all,
  static all (iterable) {
    let results = [] // 存放完成结果
    let index = 0 // 当前处理索引值
    return new MyPromise((resolve, reject) => {
      // 判断是否是可迭代对象
      if (iterable[Symbol.iterator]().next().done) {
        return resolve([])
      }

      for (const item of iterable) {
        // 使用resolve方法,无论是promise对象还是值,都会被包装成一个promise对象
        MyPromise.resolve(item).then(value => {
          results[index] = value
          index++
          if (index === iterable.length) {
            resolve(results)
          }
        }, reason => {
          reject(reason)
        })
      }
    })
}
复制代码

6.4 race()

方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个

// 静态方法race()
static race (iterable) {
    return new MyPromise((resolve, reject) => {
      for (const item of iterable) {
        MyPromise.resolve(item).then(value => {
          resolve(value)
        }, reason => {
          reject(reason)
        })
      }
    })
}
复制代码

6.5 allSettled()

allSettled()同样接受一个promise的iterable类型(Array, Map, Set),只返回一个promise实例,只有等到所有的promise实例都返回结果才结束,返回的promise实例对象只有一个fulfilled状态,返回的结果是一个对象数组,对象中存储着promise的状态,用键值status,如果是fulfilled状态用value键值储存返回值,如果是rejected状态,用reason键值储存原因

static allSettled (iterable) {
    let results = [] // 存放完成结果
    let index = 0 // 当前处理索引值
    return new MyPromise((resolve, reject) => {
      // 判断是否是可迭代对象
      if (iterable[Symbol.iterator]().next().done) {
        return resolve([])
      }

      for (const item of iterable) {
        // 使用resolve方法,无论是promise对象还是值,都会被包装成一个promise对象
        MyPromise.resolve(item).then(value => {
          results[index] = {
            status: 'fulfilled',
            vaule: value
          }
          index++
          if (index === iterable.length) {
            resolve(results)
          }
        }, reason => {
          results[index] = {
            status: 'rejected',
            reason: reason
          }
          index++
          if (index === iterable.length) {
            resolve(results)
          }
        })
      }
    })
}
复制代码

通过手写MyPromise对Promise原理有了进一步的理解,对then回调函数中return一个值的时候有了更明确的判断,下一篇会列举一些比较常见的Promise题目来结合今天所写的分析下,为什么是这样的一个返回结果

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享