Promise原理解析值得拥有

31次阅读

共计 5740 个字符,预计需要花费 15 分钟才能阅读完成。

Promise 原理解析

作者: HerryLo

本文永久有效链接: https://github.com/AttemptWeb……

Promises对象被用于表示一个异步操作的最终完成 (或失败), 及其结果值。主要是为了解决异步操作的问题。

Promise 对象的状态

一个 Promise 对象有以下三种状态:

pending: 初始状态,既不是成功,也不是失败状态。fulfilled(resolved): 意味着操作成功完成。rejected: 意味着操作失败。

Promise 对象内部运行的一个变化,变化如下:

1. 当 new Promise()被实例化后,即表示 Promise 进入 pending 初始化状态,准备就绪,等待运行。2. 一旦 Promise 实例运行成功或者失败之后,实例状态就会变为 fulfilled 或者 rejected,此时状态就无法变更。

Promise 函数使用

任何系统或函数都可以简化为输入输出系统,数据输入 ——> 黑箱 ——> 输出,如下图:

我们可以拿上图来类比Promise 函数,代码如下:

// 实例化 Promise
new Promise((resolve, reject)=> {
    // 输入
    AjaxRequest.post({
        url: 'url',
        data: {},
        sueccess: ()=> {
            // resolve
            resolve(res)
        },
        fail: (err)=> {
            // reject
            reject(err)
        }
    })
}).then((res)=> {
    // res 输出
    // ... 操作
}).catch((err)=> {
    // err 输出
    // ... 操作
})

在上面的代码中,Promise 函数参数可以作为 输入信息 ,而后经过 Promise 的内部处理( 黑箱 ),在then 函数或者 catch 函数参数中 输出信息 ,这是一个完整的系统( 别被它分散了注意力,这个解释的目的:让你更加关注 Promise 函数内部实现)。下面我们将解析 Promise 中黑箱操作。

pending 状态下会运行的函数

Promise 函数实例化,会先进入到 pending 状态,在这个状态下,它会运行如下函数:

  1. 实例化 Promise 构造函数
  2. then 方法注册回调函数
  3. catch 方法注册回调函数
  4. 调用 doResolve 函数执行 fn

实例化 Promise 构造函数

你可以直接查看源码:Promise 函数:54 行,对照阅读,同时,在下面的代码中我会做不必要的省略。

// 首先运行,Promise 构造函数
function Promise(fn) {
    // ... 省略检验

    // _deferreds 的类型,1 是 single,2 是 array
    this._deferredState = 0;
    // 0 - pending
    // 1 - fulfilled(resolved)
    // 2 - rejected
    // 3 - 另一个 Promise 的状态
    this._state = 0;
    // promise 执行结果
    this._value = null;
    // then 注册回调数组
    this._deferreds = null;
    // fn 等于 noop 即 return
    if (fn === noop) return;
    // 接受 Promise 回调函数 和 this 作为参数
    doResolve(fn, this);
}

Promise构造函数,会初始化属性,其中参数 fn 就是我们传入的函数。其中 doResolve 函数接受 Promise 函数参数this 作为参数,this 指向它自己,负责执行 fn 函数。等下面的 then 函数和 catch 函数的回调函数注册完之后,doResolve函数将立即执行。

then 方法注册回调函数

可以查看代码,查看源码:then 函数:72 行。then 方法的回调函数会被存储在 this._deferreds仔细阅读代码中的备注

Promise.prototype.then = function(onFulfilled, onRejected) {if (this.constructor !== Promise) {
        // safeThen 函数也是通过调用 handle 函数,return 新的 Promise 对象
        return safeThen(this, onFulfilled, onRejected);
    }
    // 生成新的 Promise 对象
    var res = new Promise(noop);
    handle(this, new Handler(onFulfilled, onRejected, res));
    return res;
};

// Handler 构造函数
// 它的作用是挂载 then 中的回调函数 和 一个空的 Promise 对象
function Handler(onFulfilled, onRejected, promise){
    // then 中的 Fulfilled 回调函数
    this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // then 中的 Rejected 回调函数
    this.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 保存新的 Promise
    this.promise = promise;
}
// 保存 then 注册回调函数,更新回调函数状态
function handle(self, deferred) {
    //。。。省略

    // pedding 状态 
    if (self._state === 0) {// deferred == new Handler(onFulfilled, onRejected, res)
        if (self._deferredState === 0) {
            self._deferredState = 1;
            // 存储 then 回调 deferred 对象
            self._deferreds = deferred;
            return;
        }
        if (self._deferredState === 1) {
            self._deferredState = 2;
            // 存储 then 回调 deferred 对象
            self._deferreds = [self._deferreds, deferred];
            return;
        }
        // 存储 then 回调函数对象
        self._deferreds.push(deferred);
        return;
    }

    // 只有当进入到非 pedding 状态,handleResolved 才会运行
    handleResolved(self, deferred);
}

Handler函数生成一个 deffer 对象,用于保存 then 函数中的 onFulfilled 和 onRejected 回调,以及返回的新的 promise 实例

then方法中的核心函数就是 handle 函数,它负责接收 thisnew Handler对象。若在 pedding 状态下,handle函数只负责注册回调函数,更新回调函数状态 。在非pedding 状态下,则会执行 handleResolved 函数。

catch 方法注册回调函数

查看源码:catch 函数:105 行

Promise.prototype['catch'] = function (onRejected) {return this.then(null, onRejected);
};

catch方法的回调函数实际是通过 then 方法来完成保存的。

调用 doResolve 函数执行 fn

负责运行 Promise 实例对象中的回调函数参数 fn。

// 调用 doResolve 函数
function doResolve(fn, promise) {
    var done = false;
    
    // tryCallTwo 函数执行 类似于
    // (resolve, reject) => {if(err){reject(err);return};resolve(res)}执行;
    var res = tryCallTwo(fn, function (value) {if (done) return;
        done = true;
        resolve(promise, value);
    }, function (reason) {if (done) return;
        done = true;
        reject(promise, reason);
    });

    // fn 函数调用失败,手动运行 reject 函数
    if (!done && res === IS_ERROR) {
        done = true;
        reject(promise, LAST_ERROR);
    }
}

doResolve是同步直接调用传入的函数。其中 tryCallTwo 函数作用是调用函数 fn,它接受三个参数。先执行 fn 函数,根据结果,再执行resolve 函数或 reject 函数。在 resolve 函数或 reject 函数被调用之前,Promise 对象的状态依然是pending

pending 状态下函数调用基本流程如下:

进入 resolve 或 reject 状态时会运行的函数

当初始化完之后,fn 函数执行完成,接下来就会运行 resolve 函数或者 reject 函数。

  1. 调用 resolve 函数
  2. 调用 finale 函数
  3. 调用 handleResolved 函数

调用 resolve 函数

若 Promise 对象的 fn 函数执行正常,之后就会调用 resolve 函数。可以查看源码:resolve 函数:131 行。

function resolve(self, newValue) {
    //。。。省略
    
    // newValue 存在 & (newValue 是一个对象 || newValue 是一个函数)
    if (
        newValue &&
        (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
        // 获取 then 函数
        var then = getThen(newValue);
        //。。。省略

        if (
            then === self.then &&
            newValue instanceof Promise
        ) {
            // 如果 newValue 是一个 Promise 对象,那么调用 finale 函数
            self._state = 3;
            self._value = newValue;
            finale(self);
            return;
        } else if (typeof then === 'function') {
            // 如果 newValue 是一个函数,就继续调用 doResolve 函数
            doResolve(then.bind(newValue), self);
            return;
        }
    }
    // 标记完成,进入结束流程
    self._state = 1;
    self._value = newValue;
    finale(self);
}

确认 newValue 的值,如果 newValue 是一个函数,就继续循环调用 doResolve 函数;如果 newValue 是一个 Promise 对象,那么就直接调用 finale 函数。都不是,则直接调用 finale 函数。

调用 finale 函数

进入结束流程,finale 结束。

function finale(self) {
    // 单个回调
    if (self._deferredState === 1) {
        // 执行 handle 函数,实际是执行 handleResolved
        handle(self, self._deferreds);
        self._deferreds = null;
    }
    // 回调数组
    if (self._deferredState === 2) {for (var i = 0; i < self._deferreds.length; i++) {
            // 执行 handle 函数,实际是执行 handleResolved
            handle(self, self._deferreds[i]);
        }
        self._deferreds = null;
    }
}

finale函数表示进入结束流程,执行 handle 函数。同时在上面已经说到,在非 pedding 状态下,执行 handle 函数,实际会是执行 handleResolved 函数

调用 handleResolved 函数

handleResolved负责收尾工作,负责执行 then 或者 catch 方法注册的回调函数。仔细阅读代码中的备注

var asap = require('asap/raw');

function handleResolved(self, deferred) {asap(function() {
        var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
        // 不存在 onFulfilled & onRejected
        // deferred.promise 只是一个空的 Promise 对象
        if (cb === null) {// 1 - fulfilled(resolved)
            if (self._state === 1) {resolve(deferred.promise, self._value);
            } else {reject(deferred.promise, self._value);
            }
            return;
        }
        // 执行 cb 回调函数
        var ret = tryCallOne(cb, self._value);
        if (ret === IS_ERROR) {
            // 错误,报 reject
            reject(deferred.promise, LAST_ERROR);
        } else {resolve(deferred.promise, ret);
        }
    });
}

通过异步 asap 调用,若不存在 onFulfilledonRejected,直接调用 resolvereject。若存在,则 tryCallOne 回调的结果,直接调用 resolvereject。其中的 deferred 就是上文提到的 new Handler 实例对象。真正会影响最后这步流程的,其实是 deferred.onFulfilled 或者 deferred.onRejected的回调执行,执行完回调后,这个 Promise 的执行过程就基本完成。

reject 函数在这里我就不说了,有兴趣的可以看查看源码:reject 函数

Promise 对象调用函数的基本流程图,只是一个大致的走向,便于理解:

参考

Promises/A+ 规范

MDN 中文: Promise 对象

Github: then/promise 源码

tc39: tc39 ecma262 promise

感谢

掘金:代码君的自由:解读 Promise 内部实现原理

简书:乌龟怕铁锤:Promise 源代码解析

ps: 微信公众号:Yopai,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐

正文完
 0