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 写代码,感觉非常赞!

参考

原文地址:https://www.cnblogs.com/daqianduan/p/4625499.html