zoukankan      html  css  js  c++  java
  • 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,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐

  • 相关阅读:
    iOS-延迟操作方法总结
    IOS开发调整UILabel的行间距
    day16 包和random模块 time模块 进度条
    day15 软件开发规范 日志输出和序列化反序列化
    day14 列表生成式 生成器表达式 模块
    day13 函数三元表达式,递归
    day11 装饰器
    day10作用域与闭包
    day9 函数的形参和实参
    day8 函数
  • 原文地址:https://www.cnblogs.com/liuheng/p/11599563.html
Copyright © 2011-2022 走看看