• Promise实现原理


    Promise实现原理

    这两天在熟悉 kissy 框架的时候,看到了 Promise 模块。 Promise 对于一个Jser并不陌生, Promise 类似于一个事务管理器,它的作用就是将各种内嵌回调的事务用流水形式表达。利用 Promise 可以让异步编程更符合人的直觉,让代码逻辑更加清晰,把开发人员从回调地狱中释放出来。这么“高大上”的东西,以前写 nodejs 代码的时候只是简单的用用,还没有理解其基本的实现原理,罪过!个人认为,理解编程思想最好的途径就是阅读一份简易的实现源码。很幸运,网上有不少Promise的简易实现,其中 这篇博文 介绍的实现方式非常赞,下面就来好好研究下吧!

    基础概念

    目前, Promise 是 ECMAScript 6 规范的重要特性之一,各大浏览器也开始慢慢支持这一特性。当然,也有一些第三方内库实现了该功能,如: Q 、 when 、 WinJS、 RSVP.js 等。

    Promise 对象用来进行延迟( deferred )和异步( asynchronous )计算.。一个Promise 处于以下四种状态之一:

    • pending: 还没有得到肯定或者失败结果,进行中
    • fulfilled: 成功的操作
    • rejected: 失败的操作
    • settled: 已被 fulfilled 或 rejected

    Promise 对象有两个重要的方法,一个是 then ,另一个是 resolve :

    • then:将事务添加到事务队列中
    • resolve:开启流程,让整个操作从第一个事务开始执行

    Promise 常用方式如下:

    var p = new Promise(function(resolve, reject) {  
      ...
      // 事务触发
      resovle(xxx);
      ...
    });
    p.then(function(value) {  
       // 满足
      }, function(reason) {
      // 拒绝
    }).then().then()...
    
    

    示意图如下:

    实现步骤

    1. Promise 其实就是一个状态机。按照它的定义,我们可从如下基础代码开始:

    var PENDING = 0;  // 进行中  
    var FULFILLED = 1; // 成功  
    var REJECTED = 2;  // 失败
    
    function Promise() {  
      // 存储PENDING, FULFILLED或者REJECTED的状态
      var state = PENDING;
    
      // 存储成功或失败的结果值
      var value = null;
    
      // 存储成功或失败的处理程序,通过调用`.then`或者`.done`方法
      var handlers = [];
    
      // 成功状态变化
      function fulfill(result) {
        state = FULFILLED;
        value = result;
      }
      // 失败状态变化
      function reject(error) {
        state = REJECTED;
        value = error;
      }
    }
    

    2.下面是 Promise 的 resolve 方法实现:

    注意: resolve 方法可接收的参数有两种:一个普通的值/对象或者一个 Promise对象。如果是普通的值/对象,则直接把结果传递到下一个对象;如果是一个Promise 对象,则必须先等待这个子任务序列完成。

    function Promise() {  
        ...
        function resolve(result) {
          try {
            var then = getThen(result);
            // 如果是一个promise对象
            if (then) {
              doResolve(then.bind(result), resolve, reject);
              return;
            }
            // 修改状态,传递结果到下一个事务
            fulfill(result);
          } catch (e) {
            reject(e);
          }
        }
    }
    

    两个辅助方法:

    /**
     * Check if a value is a Promise and, if it is,
     * return the `then` method of that promise.
     *
     * @param {Promise|Any} value
     * @return {Function|Null}
     */
    function getThen(value) {  
      var t = typeof value;
      if (value && (t === 'object' || t === 'function')) {
        var then = value.then;
        if (typeof then === 'function') {
          return then;
        }
      }
      return null;
    }
    /**
     * Take a potentially misbehaving resolver function and make sure
     * onFulfilled and onRejected are only called once.
     *
     * Makes no guarantees about asynchrony.
     *
     * @param {Function} fn A resolver function that may not be trusted
     * @param {Function} onFulfilled
     * @param {Function} onRejected
     */
     function doResolve(fn, onFulfilled, onRejected) {
       var done = false;
       try {
         fn(function(value) {
           if (done) return;
           done = true;
           onFulfilled(value);
         }, function(reason) {
           if (done) return;
           done = true;
           onRejected(reason);
         });
       } catch(ex) {
         if (done) return;
         done = true;
         onRejected(ex);
       }
     }
    
    

    3.上面已经完成了一个完整的内部状态机,但我们并没有暴露一个方法去解析或则观察 Promise 。现在让我们开始解析 Promise :

    function Promise(fn) {  
        ...
        doResolve(fn, resolve, reject);
    }
    

    如你所见,我们复用了 doResolve ,因为对于初始化的 fn 也要对其进行控制。fn 允许调用 resolve 或则 reject 多次,甚至抛出异常。这完全取决于我们去保证 promise 对象仅被 resolved 或则 rejected 一次,且状态不能随意改变。

    4.目前,我们已经有了一个完整的状态机,但我们仍然没有办法去观察它的任何变化。我们最终的目标是实现 then 方法,但 done 方法似乎更简单,所以让我们先实现它。

    我们的目标是实现 promise.done(onFullfilled, onRejected) :

    • onFulfilled 和 onRejected 两者只能有一个被执行,且执行次数为一
    • 该方法仅能被调用一次
    • 一旦调用了该方法,则 promise 链式调用结束
    • 无论是否 promise 已经被解析,都可以调用该方法
    var PENDING = 0;  // 进行中  
    var FULFILLED = 1; // 成功  
    var REJECTED = 2;  // 失败
    
    function Promise() {  
      // 存储PENDING, FULFILLED或者REJECTED的状态
      var state = PENDING;
    
      // 存储成功或失败的结果值
      var value = null;
    
      // 存储成功或失败的处理程序,通过调用`.then`或者`.done`方法
      var handlers = [];
    
      // 成功状态变化
      function fulfill(result) {
        state = FULFILLED;
        value = result;
        handlers.forEach(handle);
        handlers = null;
      }
      // 失败状态变化
      function reject(error) {
        state = REJECTED;
        value = error;
        handlers.forEach(handle);
        handlers = null;
      }
    
      function resolve(result) {
        try {
          var then = getThen(result);
          if (then) {
            doResolve(then.bind(result), resolve, reject)
            return
          }
          fulfill(result);
        } catch (e) {
          reject(e);
        }
      }
    
      // 不同状态,进行不同的处理
      function handle(handler) {
        if (state === PENDING) {
          handlers.push(handler);
        } else {
          if (state === FULFILLED &&
            typeof handler.onFulfilled === 'function') {
            handler.onFulfilled(value);
          }
          if (state === REJECTED &&
            typeof handler.onRejected === 'function') {
            handler.onRejected(value);
          }
        }
      }
    
      this.done = function (onFulfilled, onRejected) {
        // 保证异步
        setTimeout(function () {
          handle({
            onFulfilled: onFulfilled,
            onRejected: onRejected
          });
        }, 0);
      }
    
      doResolve(fn, resolve, reject);
    }
    

    当 Promise 被 resolved 或者 rejected 时,我们保证 handlers 将被通知。

    5.现在我们已经实现了 done 方法,下面实现 then 方法就很容易了。需要注意的是,我们要在处理程序中新建一个 Promise 。

    this.then = function (onFulfilled, onRejected) {  
      var self = this;
      return new Promise(function (resolve, reject) {
        return self.done(function (result) {
          if (typeof onFulfilled === 'function') {
            try {
              // onFulfilled方法要有返回值!
              return resolve(onFulfilled(result));
            } catch (ex) {
              return reject(ex);
            }
          } else {
            return resolve(result);
          }
        }, function (error) {
          if (typeof onRejected === 'function') {
            try {
              return resolve(onRejected(error));
            } catch (ex) {
              return reject(ex);
            }
          } else {
            return reject(error);
          }
        });
      });
    }
    

    测试

    完成了上面的代码,测试就很容易啦。偷个懒,测试实例来自MDN:

    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
      <meta charset="UTF-8">
      <title>promise test</title>
      <script src="./mypromise.js"></script>
    </head>  
    <body>  
      <button id="test">promise test</button>
      <div id="log"></div>
      <script>
        var promiseCount = 0;
        function testPromise() {
          var thisPromiseCount = ++promiseCount;
          var log = document.getElementById('log');
          log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 开始(同步代码开始)');
          var p1 = new Promise(
            function(resolve, reject) {
              log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Promise开始(异步代码开始)');
              window.setTimeout(function() {
                resolve(thisPromiseCount);
              }, Math.random() * 2000 + 1000);
            }
          );
          p1.then(
            function(val) {
              log.insertAdjacentHTML('beforeend', val + ') Promise被满足了(异步代码结束)');
            }
          );
          log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 建立了Promise(同步代码结束)');
        }
        document.querySelector('button').addEventListener('click', testPromise);
      </script>
    </body>  
    </html>  
    
    

    效果:

    结语

    通过一份简易的实现代码,理解 Promise 原理还是挺容易的。本文所有代码请 戳这 !

    参考

  • 相关阅读:
    用 jQuery Masonry 插件创建瀑布流式的页面
    ecshop标签大全 各个页面常用标签大全
    Logistic Regression Using Gradient Descent -- Binary Classification 代码实现
    Linear Regression Using Least Squares Method 代码实现
    eigen quick reference
    Linear Regression Using Gradient Descent 代码实现
    eigen 笔记2
    eigen 笔记1
    Floyd 判圈算法
    SQL Expression Language Tutorial 学习笔记二
  • 原文地址:https://www.cnblogs.com/lenther2002/p/5134803.html
Copyright © 2020-2023  润新知