最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 根据Promises/A+规范实现Promise(下)

    正文概述 掘金(Encorehwang)   2021-01-26   391

    在上一篇文章中我们已经把Promise库的一些基本内容都实现了,接下来我们在之前的基础上再继续增加内容。本文章描述Proimse库完整版代码已发布至个人github

    1. 给Promise增加静态方法deferred

    我们在之前的基础上给Promise构造函数上增加一个静态方法deferred(此方法在官方的Promise定义中并不存在,它是用来处理异步逻辑的另一种写法,跟常规的promise有一点点不同),Promise.deferred用法如下:

    let dfd = Promise.deferred();
    $.ajax({
      type: 'get',
      url: 'xxx'
    })
      .done(res => dfd.resolve(res))
      .fail(err => dfd.reject(err));
    
    dfd.promise.then(res => console.log(res));
    

    相比于常规写法,Promise.deferred方法会减少一次new Promise和减少一个函数嵌套,看起来稍微简洁一下,不过这种用法现在也基本不用了,因为有更好的async-await语法

    那么我们接下来实现一下这个Promise.deferred方法,为什么要实现该方法呢?因为我们待会需要通过一个测试库来测试这个Promise库是否符合Promises/A+规范,而该测试库需要我们提供这个Promise.deferred作为测试入口

    Promise.deferred = Promise.defer = function() {
      let dfd = {};
      dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
      });
      return dfd;
    }
    

    如上所示,deferred方法内部逻辑并不复杂,接下来我们可以使用测试库进行测试了

    2. Promise-aplus-tests

    Promise有一个配套的测试脚本,它可以帮助我们测试自己写的Promise库是否符合Promises/A+规范,使用步骤如下:

    1) 安装该库

    2) 运行测试库,测试promise代码

    后面这个promise.js替换为自己编写的库的入口文件。注意该文件末尾需要用commonjs的语法将promise输出module.exports = Promise,通过全局命令运行时是不接受esModule的语法的输出的

    当执行完上面的命令后如果给我们打印的是下面的语句就说明我们写的这个库是能通过Promises/A+规范检测的了 根据Promises/A+规范实现Promise(下)

    3. catch方法

    我们习惯于用catch方法来捕获前面promise中抛出的错误

    let promise = new Promise((resolve, reject) => {
      reject(100)
    });
    promise.then(res => 
      console.log(res)
    ).catch(err => 
      console.log(err)
    );
    

    catch方法其实可以看做一个语法糖,它等价于promise.then(null, err => console.log(err)),所以我们按照这个思路实现一下

    Promise.prototype.catch = function _catch(onError) {
      return this.then(null, onError)
    }
    

    4. 调用resolve时传入promise的场景

    到目前为止我们实现的Promise库还缺少了一种场景的考虑,也就是一个promise对象执行resolve方法时传入另一个promise对象。该场景如下所示:

    let promise = new Promise((resolve, reject) => {
      resolve(new Promise((_resolve, _reject) => {
        setTimeout(() => {
          _resolve(123);
        }, 2000)
      }));
    });
    promise.then(res => {
      console.log(res);
    }, err => {
      console.log(err);
    })
    

    按照当前的逻辑,当执行resolve方法时会将这个新创建的promise2对象赋值给外层promise的value,那么外层promise状态已经变更为fulfilled了,那么它执行promise.then(onFulfilled, onRecjected)的时候就会立即调用onFulfilled函数(也就是说promise2的executor中的setTimeout 2s对于promise的执行毫无影响),且onFulfilled函数接收到的参数是pending状态的promise2,这很明显不符合promise的定义的。

    这里正确的逻辑应该是调用resolve时如果参数是一个新的promise对象(我们称之为promise2)时,外部的promise需要等待这个promise2的状态从pending变更为fulfilled或rejected后,也跟着变更状态,并且继承promise2的value作为其value或者继承promise2的reason作为其reason,此时再执行then函数中传递的onFulfilled或onRejected。

    也就是说这里promise2设了一个2s的定时器的话,外部promise对象也需要等待2s后promise2状态变更了,才会执行其onFulfilled或onRejected方法

    那么我们来稍微改造一下Promise构造函数中的resolve方法,让它在接收到一个promise对象时也能正常处理

    function Promise(executor) {
      // ...
      const resolve = value => {
        if (value instanceof Promise) {
          value.then(resolve, reject);
          return;
        }
    
        if (this.status === 'PENDING') {
          // ...
        }
      };
    
      const reject = reason => {
        // ...
      };
      
      try {
        executor(resolve, reject);
      } catch(err) {
        reject(err);
      }
    }
    

    如上面代码所示,当执行resolve方法时接收到的是一个promise对象的话,那么我们调用value.then(resolve, reject),目的是等待这个value指向的promise2对象的状态进行变更,然后将当前promise对象的resolve和reject方法作为参数传入,当promise2对象变更为fulfilled或rejected时,就会分别调用当前promise对象的resolve和reject方法来改变其状态,并且promise2的value就能作为resolve的参数,promise2的reason就能作为reject的参数,并传递给promise对象,这样promise就能继承了promise2的value和reason了。

    这样的话,promise.then中的onFulfilled方法就会在2秒后才执行,并且它接收到的参数就是内部的promise2的value: 123。

    5. Promise.resolve和Promise.reject

    Promise上有两个静态方法resolve和reject,分别用于创建一个fulfilled状态的promise和rejected状态的promise。这个相对比较简单,我们直接来看内部逻辑

    Promise.resolve = function resolve(value) {
      return new Promise(resolve => {
        resolve(value);
      });
    } 
    Promise.reject = function reject(reason) {
      return new Promise((_, reject) => {
        reject(reason);
      })
    }
    

    6. Promise.all

    Promise.all是一个并发执行多个promise的方法,使用示例如下

    let p1 = new Promise(resolve => {resolve(100)});
    let p2 = new Promise(resolve => {
      setTimeout(() => {
        resolve(200);
      }, 1000);
    });
    let p3 = 300;
    
    Promise.all([p1, p2, p3]).then(res => {
      console.log(res);	// [100, 200, 300]
    });
    

    Promise.all方法接收的参数是一个数组,方法返回值是一个新的promise,因此可对其调用.then.catch

    若传入的数组中所有的元素都是fulfilled状态的promise对象或者普通值,则返回的新promise状态为fulfilled,若传入的数组中存在Error对象或者rejected状态的promise,则方法返回的新promise状态为rejected。内部实现如下:

    function isPromise(value) {
      if (typeof value === 'object' && value !== null || typeof value === 'function') {
        return typeof value.then === 'function';
      } else {
        return false;
      }
    }
    Promise.all = function all(promises) {
      // Promise.all方法返回的还是一个promise,所以这里return new Promise
      return new Promise((resolve, reject) => {
        let arr = [];	// 处理后的结果集,将会作为返回的proimse的value
        let count = 0;	// 计算已经有多少个promise经过处理了
    
        function processData(index, value) {
          arr[index] = value;
          if (++count === promises.length) {
            // 当结果集的元素个数与传入的数组长度相等,说明所有promise都已经处理完,可以将返回的promise对象变更为fulfilled状态了
            // 且该结果集作为返回promise的value
            resolve(arr);
          }
        }
        
        for (let i=0; i<promises.length; i++) {
          let current = promises[i];
          if (isPromise(current)) {
            // 是promise对象的话,调用其then方法等待该promise状态发生变更
            current.then(data => {
              processData(i, data);
            }, err => 
              reject(err) // 如果传入的参数中有任何一个promise是rejected的,直接让最终返回的promise对象变为rejected
            )
          } else {
            // 是一个普通值就直接放入结果集
            processData(i, current);
          }
        }
      })
    }
    

    大致逻辑如上所示,使用一个count变量来计算已经有多少个元素经过处理了,如果全部都处理完了,则将返回的promise变更为fuflilled状态,如果处理过程中有任何一个promise是rejected状态,则将返回的promise置为rejected状态

    7. Promise.race

    Promise.race方法接收一个数组,将数组中的多个promise/普通值进行处理并且返回一个新的promise对象,它会取多个promise对象中最快变更状态的一个promise对象的状态作为最终的结果,使用示例如下

    let p1 = new Promise(resolve => {
      setTimeout(() => {
        resolve(100)
      }, 500);
    });
    let p2 = new Promise(resolve => {
      resolve(200);
    });
    Promise.race([p1, p2]).then(res => {
      console.log(res);	// 200
    })
    

    如上所示,p1有延时操作而p2没有,p2的状态更早发生变更,那么Promise.race返回的promise对象就会取p2的结果作为结果。那么我们来实现下Promise.race的内部逻辑

    function isPromise(value) {
      if (typeof value === 'object' && value !== null || typeof value === 'function') {
        return typeof value.then === 'function';
      } else {
        return false;
      }
    }
    Promise.race = function race(promises) {
      return new Promise((resolve, reject) => {
        for (let i=0; i<promises.length; i++) {
          let current = promises[i];
    	  if (isPromise(current)) {
    	    // current是一个promise对象的话,等待其变更状态,然后将要返回的promise的状态变更为与current的状态一样即可
    	    current.then(resolve, reject);
    	  } else {
    	    // current是普通值的话直接将要返回的promise变更为fulfilled状态,且这个普通值就是proimse的value
            resolve(current);
          }
        }
      })
    }
    

    到此为止,我们的Promise库就基本实现完成了,这里还有一些Promise比较不常用的API就没有实现,另外也可能存在一些比较复杂的场景没有考虑到,大家可以基于此基础再做扩展。

    参考资料

    • Promises/A+规范
    • Promises/A+测试工具
    • Promise库完整实现代码

    起源地下载网 » 根据Promises/A+规范实现Promise(下)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元