手把手带你实现一个符合promiseA+规范的promise类库

前言

我们都知道javascript是一门单线程的脚本语言。不能创建线程,不能开展并行任务,不能对线程操作。在页面加载时会阻塞ui渲染。但是,虽然js是单线程语言,我们的浏览器却不是单线程的。我们可以利用异步线程来解决这个问题。这也正是为什么异步编程对于js来说是非常重要的原因。

什么是promise

首先,我们要知道,什么是promise。promise是一个为了解决异步编程而被提出来的解决方案,他最早是由社区提出来的,在ES6中,被写进了语言标准,统一了它的用法,原生提供了Promise对象。

Promsie为什么会被提出?

传统的解决异步编程的方案是什么呢?

  1. setTimeout、setInterval、setImmediate ( ps:html5标准规定 setTimeout的最小执行间隔是4ms,而setInterval的最小执行间隔是10ms) 这个方案的问题在于,它并非那么精确,以setTimeout为例
    setTimeout(()=>{console.log('我执行了')})
    for(let i=0;i<10000000000;i++){};
复制代码
  • 控制台会在4ms后将 '我执行了' 打出吗?显然不会,他要等到当前执行栈中的代码执行完之后才会执行,也就是,他要等到for循环结束之后才能执行异步的代码。
  1. 事件监听
    整个程序都要变成事件驱动型,程序的运行机制将会变得很不清晰。
  2. 回调函数
    js中对于异步编程的实现,基本上就是回调函数,但是层层嵌套的回调函数可读性非常差,错误处理也很不清晰。想起被回调地狱控制的恐惧吧。
    $.ajax('a.json',function(data){
        $.ajax(data,function(data){
            $.ajax(data,function(data){
                $.ajax(data,function(data){
                    $.ajax(data,function(data){
                        console.log(data)
                    })
                })
            })
        })
    })
复制代码
  • 解决方案
    为了解决这个问题es6、7把Promise、Generator以及两者的进阶async函数写入了语言标准, Promise的写法是回调函数的改进,使用Promise实例的then方法,可以将异步回调的嵌套关系转变为链式调用。
let promise = new Promise((resolve,reject)=>{
    setTimeout(resolve,3000)
})
promise.then(data=>{
    console.log(data)
},err=>{
    console.error(err)
}).then()
复制代码

Promsie写起来

废话不多说了,相信点进这篇文章的coder应该对于Promise本身已经很熟悉了(对这一块存有疑虑的,可以先去开一下阮老师的es6入门),那么我们赶紧把来实现一下我们自己的Promise库吧.

class Promise {
  constructor(executor) {
    // 一个Promise实例创建出来时,他的默认状态一定是等待态 pending
    this.status = 'pending';
    // 成功时的不可变值
    this.value = undefined;
    // 失败的原因
    this.reason = undefined;
    /**
     * 两个数组 onResolvedCakkbacks和onRejectedCallbacks
     * 用来存放当前promise实例还处于pending状态时添加的成功回调函数
     * 以及失败的回调函数
     * */
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = data => {
      /**
       * 确认只有当状态为pending时,改变当前promise实例的状态为fullfilled
       * 并且执行存放在成功回调数组里面的回调函数
       * */

      if (this.status === "pending") {
        // 确定不可变值
        this.value = data;
        // 固定当前实例的状态
        this.status = 'fulfilled';
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    let reject = err => {
      /**
      * 确认只有当状态为pending时,改变当前promise实例的状态为rejected
      * 并且执行存放在成功回调数组里面的回调函数
      * */
      if (this.status === 'pending') {
        // 获取失败原因
        this.reason = err;
        // 固定当前实例的状态
        this.status = 'rejected';
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    /**
     * 创建Promise实例时,将会直接运行他的执行函数,
     * 并且,如果在执行过程中出现了异常,
     * 将会导致该promise实例的状态变为rejected
     * */
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e); // 将错误原因传递出去
    }
  }
  /**
   * Promise实例上的原型上有一个then方法,then方法上有两个参数,分别是
   * 当该实例状态为fulfilled时或者rejected时的回调
   * */
  then(onFulFilled, onRejected) {
    /**
     * 由于onFulFilled和onRejected可能存在不是函数的问题
     * 所以要对这两个参数进行判断
     * */
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; // 没有传递错误回调时,将错误原因向外抛出
    /**
     * 申明一个变量promise2,为什么要叫promise2? (黑人问号????)
     * 嗯,这是个好问题,因为这是Promise A+规范里面规定的,
     * 同时,规范还规定了,每个Promise的实例的then方法都会返回一个
     * 新的Promise实例,注意,这个新的实例Promsie并不是我们原来的那个Promise实例
     * */
    let promise2;
    // 当前状态为pending时
    if (this.status === 'pending') {
      promise2 = new Promise((resolve, reject) => {
        // 存放成功的回调
        this.onResolvedCallbacks.push(() => {
          /**
           * 在原生es6中,异步运行回调时,Promise.then属于微任务,
           * 但是我们现在只能使用setTimeout来模拟这个异步回调,
           * 导致这里成为了一个宏任务,虽然和原声的Promise对象有点区别,
           * 但是我们的这个Promise仍然是符合 Promise A+规范的
           * 如果我们是在node环境下,也许我们可以使用process.nexttick来进行模拟
           * 这样将会和原声Promise对象表现的更加一致
           * 关于宏任务和微任务的区别,这又要和js事件环联系起来,由于篇幅有限,
           * 大家可以去了解下js事件循环相关的知识
           * */

          setTimeout(() => {
            /**
             * 获取onFulfilled(this.value)的返回值x,
             * 我们需要一个方法来检测,这个返回值x到底是什么东西,
             * 他是不是一个promise对象?如果他不是promise对象,
             * 那么我们就可以直接把x作为promise2的fulfilled回调函数的参数传过去
             * */
            try {
              let x = onFulFilled(this.value);

              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e) // 如果运行异常,那么直接让promise2的状态固定为rejected,并把错误原因传递出去
            }
          });
        });
        // 存放失败的回调
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
      })
    }
    // 当状态为成功时
    if (this.status === 'fulfilled') {
      promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulFilled(this.value);
            //  解析x和promise2的关系
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            //  异常时失败
            reject(e);
          }
        })
      })
    }
    if (this.status === 'rejected') {
      promise2 == new Promise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e);
          }
        });
      });
    }
    return promise2;  // Promise实例的then方法,总是返回一个新的Promise
  }
}

// 定义检测x与promise2的方法
function resolvePromise(promise2, x, resolve, reject) {
  /**
   * 首先我们要判断x是不是Promise2在规范中,规定了一段代码,这个代
   * 码可以实现我们的promise和别人的promise可以进行交互,但是我们不知道
   * 别人的promise的实现会不会出现 promise2 === x 这种情况的出现,如果
   * 出现这种情况会出现循环引用的问题, promise2 === x,这样,promise2要等待x
   * 的状态改变,而x就是它本身,这样循环引用promise2的状态就会在这里停滞,一直
   * 处于pending状态
   * */
  if (promise2 === x) {
    return reject(new TypeError('循环引用'))
  }
  // 对x的数据类型进行判断,当x为对象或者函数时,我们需要对它进行判断
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let called; //防止成功后调用失败方法
    /**
     * 规范要求声明一个变量then,并将x的then属性赋给该变量
     * 这个时候我们需要使用try catch来运行这段代码,因为,在这个过程中如果发生了异常,
     * 我们需要把异常给传递出去
     * 一般来说,这样一个赋值操作是不会出现异常这种问题的,但是我们的Promise类库可能会和其他人
     * 的Promise类库混用,这个时候,如果其他人的类库中x的then属性是通过Object.defineProperty定义的
     * 将可能导致出现异常(虽然正常人不会这么写代码,但保不齐刚好遇到个不正常的呢?)
     * 例: Object.defineProperty(x,'then',{
     *    set(){
     *      throw (new Error());
     *    }
     * })
     * */
    try {
      let then = x.then;
      if (typeof then === 'function') {
        /**
         * 按照规范,如果then是函数,我们就认为x是promise
         * 这时,我们通过then.call运行then方法,将this指向x
         * ,后面的是成功的回调和失败的回调
         * */
        then.call(x, y => { // 如果y是promise ,那么我们就继续递归解析
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => { //如果失败了,那么就直接将状态修改为失败
          if (called) return;
          called = true;
          reject(r);
        })
      } else { // 当then只是一个普通对象时,那么我们直接将这个对象作为promise2的不可变值传出去
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e)
    }
  } else { //当x既不是对象,也不是函数时,直接将他作为promise2的不可变值传递出去
    resolve(x)
  }
}


//测试一下
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(3333)
  }, 3000)
})
p1.then(data => {
  console.log(1, data)
}, err => {
  console.error(2, err)
})
//导出Promise
module.exports = Promise;


复制代码

最后,我们再使用npm安装一个插件,这个插件能够帮助我们检测,我们的promise类库是否符合 Promise A+规范

npm install promises-aplus-tests -g
promises-aplus-test 文件名
复制代码

附上Promise A+规范地址,大家可以看看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值