Promise实现原理

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

基础概念

目前, PromiseECMAScript 6 规范的重要特性之一,各大浏览器也开始慢慢支持这一特性。当然,也有一些第三方内库实现了该功能,最流行的如:bluebird等。
Promise 对象用来进行延迟( deferred )和异步( asynchronous )计算。一个Promise 处于以下四种状态之一:

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

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

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

Promise 常用方式如下:

1
2
3
4
5
6
7
8
9
10
11
var p = new Promise(function(resolve, reject) {
...
// 事务触发
resovle(xxx);
...
});
p.then(function(value) {
// 满足
}, function(reason) {
// 拒绝
}).then().then()...

示意图如下:
promise_method.png

实现步骤

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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 对象,则必须先等待这个子任务序列完成。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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);
    }
    }
    }

    两个辅助方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    /**
    * 检查值是否是一个Promise,如果是,返回这个Promise的 `then`方法。
    * @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;
    }
    /**
    * 确保onfulfilled和onrejected只调用一次。
    *
    * @param {Function} fn
    * @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

    1
    2
    3
    4
    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 已经被解析,都可以调用该方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    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 shandle(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);
    }

    Promiseresolved 或者 rejected 时,我们保证 handlers 将被通知。

  5. 现在我们已经实现了 done 方法,下面实现 then 方法就很容易了。需要注意的是,要在处理程序中新建一个Promise
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!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>

效果:
demo.gif

结语

通过一份简易的实现代码,理解Promise 原理还是挺容易的。全部代码