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

      这次探究可能存在问题,为了不浪费您的时间,请忽略此文。 

      先看的这篇有问题的文章

      ps: 2018年更新 这篇有问题的文章已经被作者团队删除了

      花了很长时间研究这篇文章,卡在实现串行Promise那儿了,一直看不明白。就在刚才,发现这篇文章是错的,在第一次用setTimeout( ,0)那儿就错了。虽然用setTimeout( ,0)实现了resolve的异步,但then那儿有个判断,给直接执行了,没用上这个异步。正确的代码应该是这样的

                function resolve(newValue) {
                    value = newValue;
                    // state = 'fulfilled';
                    setTimeout(function () {
                        state = 'fulfilled';
                        deferreds.forEach(function (deferred) {
                            deferred(value);
                        });
                    }, 0);
                }

      气人,浪费了好多时间,重新找一篇文章研究吧。

      无语了,找了好多promise实现原理的文章,都是错的。先去看看微软的winjs库吧,不行的话,还是要看最初那篇有问题的文章。

      发现看不懂winjs的源码,3.0的源码竟然7万多行。那还是看最初的那篇文章吧,把错误的地方找出来改掉。不想花太多时间在这上面了,因为还有很多别的知识要看,只要下面的代码运行没问题就暂时放一下,更详细的探究以后会补上。

            var promise = new PromiseB(function  (resolve) {
                resolve('aaaaa');
            });
            promise.then(function  (value) {
                console.log(value);
            })
            console.log('bbbbbb');

      只要能先输出'bbbbb'后输出'aaaaa'就当OK了。PromiseB是自己定义的一个Promise构造函数。好,下面开始大量使用那篇文章的内容。

      

    为了让语言上更加准确和简练,本文做如下约定:

    • Promise:代表由 Promises/A+ 规范所定义的异步操作封装方式;
    • promise:代表一个 Promise 实例。

    基础实现

    为了增加代入感,本文从最为基础的一个应用实例开始探索:通过异步请求获取用户id,然后做一些处理。在平时大家都是习惯用回调或者事件来处理,下面我们看下 Promise 的处理方式:

    // 例1
    
    function getUserId() {
        return new Promise(function (resolve) {
            // 异步请求
            Y.io('/userid', {
                on: {
                    success: function (id, res) {
                        resolve(JSON.parse(res).id);
                    }
                }
            });
        });
    }
    
    getUserId().then(function (id) {
        // do sth with id
    });

    getUserId 方法返回一个 promise,可以通过它的 then 方法注册在 promise 异步操作成功时执行的回调。自然、表意的 API,用起来十分顺手。

    满足这样一种使用场景的 Promise 是如何构建的呢?其实并不复杂,下面给出最基础的实现:

    function Promise(fn) {
        var value = null,
            deferreds = [];
    
        this.then = function (onFulfilled) {
            deferreds.push(onFulfilled);
        };
    
        function resolve(value) {
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }
    
        fn(resolve);
    }

    代码很短,逻辑也非常清晰:

    • 调用then方法,将想要在 Promise 异步操作成功时执行的回调放入 deferreds 队列;
    • 创建 Promise 实例时传入函数被赋予一个函数类型的参数,即 resolve,用以在合适的时机触发异步操作成功。真正执行的操作是将 deferreds 队列中的回调一一执行;
    • resolve 接收一个参数,即异步操作返回的结果,方便回调使用。

    下面是我对上面代码的一些理解补充:

      先定义一个Promise构造函数,该构造函数有一个参数,该参数是一个函数。下面开始创造Promise实例,先将函数参数传入(注意该函数暂时未调用,是放到最后调用的),然后定义2个内部变量value和deferreds,接着定义实例的then方法(该方法在以后调用的时候,需要传入一个函数,该函数会放进deferreds队列里),接着定义resolve函数(该函数暂时不能被外部访问,而是通过fn以闭包方式调用的),该函数是用来执行deferreds队列里的代码的,最后执行fn函数,fn里就是开发者在实例化一个新promise对象时传入的函数。注意,现在是有问题的,resolve在实例构造的过程中和执行的时候,deferreds队列是空的,then的函数没有传进来,这个问题后面再解决。

      接着原文章的内容

      有时需要注册多个回调,如果能够支持 jQuery 那样的链式操作就好了!事实上,这很容易:

    this.then = function (onFulfilled) {
        deferreds.push(onFulfilled);
        return this;
    };

    这个小改进带来的好处非常明显,当真是一个大收益的小创新呢:

    // 例2
    
    getUserId().then(function (id) {
        // do sth with id
    }).then(function (id) {
        // do sth else with id
    });

      注意,这样其实是有问题的,then返回的是原promise实例,这与promise 迷你书2.6节“专栏: 每次调用then都会返回一个新创建的promise对象”不相符,这个问题后面再谈。

      

    延时

    如果 promise 是同步代码,resolve 会先于 then 执行,这时 deferreds 队列还空无一物,更严重的是,后续注册的回调再也不会被执行了:

    // 例3
    
    function getUserId() {
        return new Promise(function (resolve) {
            resolve(9876);
        });
    }
    
    getUserId().then(function (id) {
        // do sth with id
    });

    此外,Promises/A+ 规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。为解决这两个问题,可以通过 setTimeout 将 resolve 中执行回调的逻辑放置到 JS 任务队列末尾:

    function resolve(value) {
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }, 0);
    }

    引入状态

    Hmm,好像存在一点问题:如果 Promise 异步操作已经成功,之后调用 then 注册的回调再也不会执行了,而这是不符合我们预期的。

    解决这个问题,需要引入规范中所说的 States,即每个 Promise 存在三个互斥状态:pending、fulfilled、rejected。经过改进后的代码:

    function Promise(fn) {
        var state = 'pending',
            value = null,
            deferreds = [];
    
        this.then = function (onFulfilled) {
            if (state === 'pending') {
                deferreds.push(onFulfilled);
                return this;
            }
            onFulfilled(value);
            return this;
        };
    
        function resolve(newValue) {
            value = newValue;
            setTimeout(function () {
                state = 'fulfilled';
                deferreds.forEach(function (deferred) {
                    deferred(value);
                });
            }, 0);
        }
    
        fn(resolve);
    }

    我把原文state = 'fulfilled'改了位置。

    resolve 执行时,会将状态设置为 fulfilled,在此之后调用 then 添加的新回调,都会立即执行。

    似乎少了点什么,哦,是的,没有任何地方将 state 设为 rejected,这个问题稍后会聊,方便聚焦在核心代码上。

    串行 Promise

    在这一小节,将要探索的是 Promise 的 Killer Feature:串行 Promise,这是最为有趣也最为神秘的一个功能。

    串行 Promise 是指在当前 promise 达到 fulfilled 状态后,即开始进行下一个 promise(后邻 promise)。例如获取用户 id 后,再根据用户 id 获取用户手机号等其他信息,这样的场景比比皆是:

    // 例4
    
    getUserId()
        .then(getUserMobileById)
        .then(function (mobile) {
            // do sth with mobile
        });
    
    function getUserMobileById(id) {
        return new Promise(function (resolve) {
            Y.io('/usermobile/' + id, {
                on: {
                    success: function (i, o) {
                        resolve(JSON.parse(o).mobile);
                    }
                }
               });
        });
    }

    这个 feature 实现的难点在于:如何衔接当前 promise 和后邻 promise。

    首先对 then 方法进行改造:

    this.then = function (onFulfilled) {
        return new Promise(function (resolve) {
            handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    };
    
    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }
    
        var ret = deferred.onFulfilled(value);
        deferred.resolve(ret);
    }

       先停一下,ES6中的链式调用是怎么传参的呢?看一下迷你书的2.4.2. “promise chain 中如何传递参数”,就是在then方法的函数参数里return一下就可以了,我们按照这个接着研究promise的原理。

      接下来的不怎么好解释了,直接看我写的代码,代码里包含了对resolve的改造,总体地看,多看几遍思路就清楚了。

            function PromiseB(fn) {
                var state = 'pending',
                    value = null,
                    deferreds = [];
    
                this.then = function (onFulfilled) {
                    return new PromiseB(function (resolve2) {
                        handle({
                            onFulfilled: onFulfilled || null,
                            resolve: resolve2
                        });
                    });
                };
    
                function handle(deferred) {
                    if (state === 'pending') {
                        deferreds.push(deferred);
                        return;
                    }
    
                    var ret = deferred.onFulfilled(value);
                    deferred.resolve(ret);
                }
    
                function resolve(newValue) {
                    if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
                        var then = newValue.then;
                        if (typeof then === 'function') {
                            then.call(newValue, resolve);
                            return;
                        }
                    }
                    // state = 'fulfilled';
                    value = newValue;
                    setTimeout(function () {
                        state = 'fulfilled';
                        deferreds.forEach(function (deferred) {
                            handle(deferred);
                        });
                    }, 0);
                }
    
                fn(resolve);
            }
            
            var promise = new PromiseB(function  (resolve) {
                resolve('aaaaa');
            });
            promise.then(function  (value) {
                console.log(value);
                return 'ccccc'
            }).then(function  (value) {
                console.log(value);
            })
            console.log('bbbbbb');

       脑子晕掉了,我已经暂时不想看到Promise这东西,暂时搁置。

  • 相关阅读:
    leetcode-13. Roman to Integer
    leetcode-171. Excel Sheet Column Number
    学生信息管理系统案例小结
    Kafka 生产者、消费者与分区的关系
    json.dumps()包装中文字符串
    Spring-JDBC
    JDBC 连接池
    JDBC
    Python DBUtils 连接池对象 (PooledDB)
    Java Junit单元测试
  • 原文地址:https://www.cnblogs.com/zhansu/p/6106107.html
Copyright © 2011-2022 走看看