zoukankan      html  css  js  c++  java
  • Promise实现原理

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

    基础概念

    目前, Promise 是 ECMAScript 6 规范的重要特性之一,各大浏览器也开始慢慢支持这一特性。当然,也有一些第三方内库实现了该功能,如: 、 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()...

    示意图如下:

    promises

    实现步骤

    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="http://www.chenjunxyf.me/promiseshi-xian-yuan-li/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-show

    结语

    通过一份简易的实现代码,理解 Promise 原理还是挺容易的。本文所有代码请 戳这 !PS:这次用了 vscode 写代码,感觉非常赞!

    参考

  • 相关阅读:
    C# MQTT M2MQTT
    C# MethodInvoker委托的使用
    linux打包/解压-tar
    linux挂载查看、添加与取消
    IE6、火狐不支持a:visited
    js和jquery中的触发事件
    MySQL5.7.9免安装版配置方法
    mysql数据库在Navicat Premium连接的时候出现1862错误
    mysql max_allowed_packet查询和修改
    服务器是windows时tomcat无法打印所有日志配置修改
  • 原文地址:https://www.cnblogs.com/daqianduan/p/4625499.html
Copyright © 2011-2022 走看看