美文网首页程序员让前端飞
Promise-Polyfill源码解析(2)

Promise-Polyfill源码解析(2)

作者: xshinei | 来源:发表于2018-10-03 18:41 被阅读8次

    在上篇文章Promise-Polyfill源码解析(1)详细分析了Promise构造函数部分的源码,本篇我们继续分析剩下的源码。
    本篇我们重点分析then方法,让我们回忆下then方法的使用方式:首先这个方法属于每个Promise对象,这说明then方法应该定义在Promise的原型链上;然后这个方法接收两个回调函数,如果Promsie的状态为已完成,则执行第一个回调,状态为被拒绝,则执行第二个回调,这个说明then方法会等待Promise状态改变才会去执行回调;最后then方法可以链式调用,如下:

    Promise.resolve().then(function() {
      // ...
    }, function() {
      // ...
    }).then(function() {
      // ...
    }, function() {
      // ...
    });
    

    了解了以上,我们来看then方法的源码:

    Promise.prototype.then = function(onFulfilled, onRejected) {
      // @ts-ignore
      var prom = new this.constructor(noop);
    
      handle(this, new Handler(onFulfilled, onRejected, prom));
      return prom;
    };
    

    正如我们所猜想的,then方法定义在Promise的构造函数上,每个Promise对象可以共享该方法。其接收两个参数onFulfilled、onRejected。具体实现也非常简洁,只有三行代码,先来看第一行:

    var prom = new this.constructor(noop);
    

    这句代码用new操作符实例化了一个对象,并保存在prom变量中。new操作符的右边一定是个构造函数,this指向当前Promise对象,其constructor属性指向构造函数,所以this.constructor指向Promise构造函数。我们知道,Promise构造函数的参数为一个函数,这里传入了noop,noop是什么?我们找到其定义:

    function noop() {}
    

    我们发现noop只是个空函数。再来看最后一行代码:

    return prom;
    

    返回了prom对象,也就是说,then方法最后返回了一个Promise对象,这也就是then方法可以链式调用的原因所在!
    有个疑问,为什么不直接返回this,而是返回新创建的Promise对象呢?其实是因为Promise的状态改变时单向的,且只能改变一次。
    然后重点来看下第二行代码:

    handle(this, new Handler(onFulfilled, onRejected, prom));
    

    调用了handle函数,先不管handle做了什么,我们先关注其第二个实参:

    new Handler(onFulfilled, onRejected, prom)
    

    其实例化了Handler对象,参数为then方法的两个参数和prom对象,我们来看下其具体实现:

    /**
     * @constructor
     */
    function Handler(onFulfilled, onRejected, promise) {
      this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
      this.onRejected = typeof onRejected === 'function' ? onRejected : null;
      this.promise = promise;
    }
    

    Handler构造函数将传入的参数分别赋值给实例对象的onFulfilled、onRejected、promise属性,其中对onFulfilled和onRejected做了处理,若不是函数类型,则赋值为null。这说明,我们传入给then方法的两个参数可以不为函数类型,其内部会调整为null。
    明白了第二个参数,我们来看handle函数具体做了什么:

    function handle(self, deferred) {
      while (self._state === 3) {
        self = self._value;
      }
      if (self._state === 0) {
        self._deferreds.push(deferred);
        return;
      }
      self._handled = true;
      Promise._immediateFn(function() {
        var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
        if (cb === null) {
          (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
          return;
        }
        var ret;
        try {
          ret = cb(self._value);
        } catch (e) {
          reject(deferred.promise, e);
          return;
        }
        resolve(deferred.promise, ret);
      });
    }
    

    首先是一个while循环:

     while (self._state === 3) {
        self = self._value;
      }
    

    self指的是当前Promise对象,如果self._state的值为3,则将self._value赋值给self。我们在上篇文章分析过,_state属性值为3,则说明_value值为一个Promise对象。那么这个循环的结果就是,直到_value属性值不为Promise对象,为什么要这么处理呢?我们来看下规范是怎么说的:
    如果 x 为 Promise,则使promise接收x的状态

    • 如果 x 处于pendding,promise需要保持为pendding状态直至x被解决或拒绝
    • 如果 x 处于fulFilled,用相同的值执行 promise
    • 如果 x 处于rejected,用相同的据因拒绝 promise
      总结起来就是,如果_value属性值为Promise对象,则结果取决于嵌套最内层Promise的状态。
      接下来是一个条件判断:
     if (self._state === 0) {
        self._deferreds.push(deferred);
        return;
      }
    

    如果self._state属性为0,则将deferred压入self._deferreds数组,并结束此次函数调用。其中deferred为传入的Handler实例对象,我们在上篇里分析过,_state属性值为0表示Promise的状态为pendding,我们可以猜测到,状态为pedding,也就是Promise的状态并未改变,then方法不知道要执行哪个回调,所在要先保存。那么为什么是保存在一个数组里,而不是保存在一个变量里,难道有很多个?其实还真可能有很多个,因为then方法可以被多次调用:


    image.png

    可以看到,每个then方法的回调都被执行了。
    再来看下面的代码:

    self._handled = true;
    

    上篇文章也分析过,_handled属性用来标记Promise是否被处理,这里将其赋值为true,说明当前Promise对象已经被处理了。
    最后来看最后一段代码:

    Promise._immediateFn(function() {
       ...
    });
    

    调用了Promise._immediateFn方法,并传入了一个回调函数。先来看Promise._immediateFn的定义:

    // Use polyfill for setImmediate for performance gains
    Promise._immediateFn =
      (typeof setImmediate === 'function' &&
        function(fn) {
          setImmediate(fn);
        }) ||
      function(fn) {
        setTimeoutFunc(fn, 0);
      };
    

    这里判断setImmediate是否是函数类型,成里则赋值为function(fn) { setImmediate(fn) },否则赋值为function(fn) { setTimeoutFunc(fn, 0) },其中setTimeoutFunc是setTimeout的别名:

    var setTimeoutFunc = setTimeout;
    

    setImmediate是Node.js里的global对象的属性,而setTimeout是浏览器环境里window对象的属性,所以Promise._immediate是兼容两个环境所做处理的代码。为什么要再包一层闭包呢?应该是兼容参数的数量。
    到这我们也明白了,then方法的回调是异步执行,其实更具体是在micro队列中,这里我们就不展开了。
    回到Promise._immediateFn的回调参数:

    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    

    上篇文章分析过,self._state属性值为1表示Promise的状态为已完成,为2表示状态为被决绝。那么这句代码的意思是,根据Promise的状态,将then方法的完成回调或决绝回调赋值给cb变量。
    再来看下面的条件判断:

    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    

    cb变量为null,也就是我们传入给then方法的参数不是函数类型,这里会根据Promise的状态执行resolve或reject函数,并结束此次调用。注意传入的参数,deferred.promise和self._value,也就是说,用Promise的值去改变在then方法内创建的Promise对象的状态。总结起来就是,若then方法未传入对应的回调,那么Promise的值会被传递到下一次then方法中:


    image.png

    再来看最后一段代码:

    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);
      return;
    }
    resolve(deferred.promise, ret);
    

    忽略try..catch,核心是这样的:

    var ret = cb(self._value);
    resolve(deferred.promise, ret);
    

    将self._value作为参数,调用cb函数,返回值保存在ret变量中,再以ret变量为参数调用resolve函数。这里的意思就是,将cb函数的返回值作为Promise的值传递给下一个then方法:


    image.png

    当然,若抛出异常,则将原因作为Promise的值,传递给下一个then方法:

    reject(deferred.promise, e);
    return;
    

    至此,Promise源码的核心部分已经分析完了,我们可以发现,阅读源码可以了解Promise的内部的工作机制,当出现问题时,我们也能快速定位原因。鼓励大家去阅读源码!
    当然还有catch、all、race等方法,将在下一篇文章继续分析。

    相关文章

      网友评论

        本文标题:Promise-Polyfill源码解析(2)

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