美文网首页
深刻理解Promise系列(一):手把手教你实现Promise(

深刻理解Promise系列(一):手把手教你实现Promise(

作者: 任无名F | 来源:发表于2017-06-26 23:33 被阅读0次

    是什么

    Promise是一种对异步操作的封装,主流的规范是Promise/A+。
    Promise可以使得异步代码层次清晰,便于理解,且更加容易维护。

    追求的效果

    那么我们的Promise将要实现怎样的效果呢?举个简单的例子如下:

    function p1() {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve(1);
        }, 1000);
      });
    }
    function p2(value) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve(2 + value);
        }, 1000);
      });
    }
    p1().then(function(res) {
      console.log(res); // 1000ms后输出1
      return Promise.resolve(res); // 显式的return一个Promise对象
    }).then(p2).then(function(res) {
      console.log(res); // 再过1000ms后输出3
    });
    

    我们手写的Promise最终也可以实现这个效果

    最基本的构建

    function Promise(fn) {
      let value = null; // 异步函数执行后的结果
      let deferred; // 异步函数执行后,真正要执行的回调函数
      this.then = function(onFulfilled) {
        deferred = onFulfilled;
      }
      function resolve(newValue) {
        value = newValue;
        deferred(value);
      }
      fn(resolve);
    }
    

    代码很少,也很容易理解:

    • 创建Promise实例的参数是fn,并将其内部的resolve方法作为参数传递给异步函数
    • Promise的then方法用于注册回调函数,即赋值给内部的deferred
    • 当异步函数回调成功后,会将结果作为参数来执行resolve,而实际上是执行deferred函数
    • 这样就实现了在恰当的时机(异步函数回调成功后),执行恰当的回调(then注册的回调方法)

    改进1

    目前的代码只能注册一个回调方法,这显然不符合我们的预期,所以将内部的deferred修改为deferreds数组,相应的执行resolve时,也要遍历deferreds数组依次执行:

    function Promise(fn) {
      let value = null;
      let deferreds = [];
      this.then = function(onFulfilled) {
        deferreds.push(onFulfilled);
      }
      function resolve(newValue) {
        value = newValue;
        deferreds.forEach((deferred) => {
          deferred(value);
        });
      }
      fn(resolve);
    }
    

    改进2

    实现then的链式调用,非常简单:

      this.then = function(onFulfilled) {
        deferreds.push(onFulfilled);
        return this;
      }
    

    这样就可以实现:

    p1().then(function(res) {
      // do sth. with res
    }).then(function(res) {
      // do sth. else with res
    });
    

    延时resolve

    目前的Promise有一个bug,假如fn中所包含的是同步代码,则resolve会立即执行,此时then还没有注册回调函数,内部的deferreds为空数组,所以回调函数不会如预期一样执行。
    所以,为resolve添加一个延时:

      function resolve(newValue) {
        value = newValue;
        setTimeout(() => {
          deferreds.forEach((deferred) => {
            deferred(value);
          });
        }, 0);
      }
    

    以上保证了resolve于then注册回调函数之后执行。

    引入状态

    目前还存在一点问题,现在用then注册回调函数的行为都是在异步操作成功之前,一旦异步操作已经成功后,内部resolve已经执行完毕,再用then方法注册回调函数就不会再执行了。
    想要解决这个问题,需要引入规范中的三个状态:pending、fulfilled、rejected,它们之间的关系是:


    Promise的三种状态

    引入状态后的代码:

    function Promise(fn) {
      let state = "pending";
      let value = null;
      let deferreds = [];
      this.then = function(onFulfilled) {
        // state若为pending则将onFulfilled加入队列
        if(state === "pending") {
          deferreds.push(onFulfilled);
          return this;
        }
        // state若为fulfilled则立即执行onFulfilled
        onFulfilled(value);
        return this;
      }
      function resolve(newValue) {
        state = "fulfilled"; // 异步操作完成后将state置为fulfilled
        value = newValue;
        setTimeout(() => {
          deferreds.forEach((deferred) => {
            deferred(value);
          });
        }, 0);
      }
      fn(resolve);
    }
    

    异步操作成功之后,state会变成fulfilled,这之后then注册的回调函数都会立即执行。

    以上基本实现了Promise的所有基础功能,但真正魔幻般的部分尚未开始,详见下篇~

    参考资料剖析 Promise 之基础篇

    相关文章

      网友评论

          本文标题:深刻理解Promise系列(一):手把手教你实现Promise(

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