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这东西,暂时搁置。

  • 相关阅读:
    【乱侃】How do they look them ?
    【softeware】Messy code,some bug of Youdao notebook in EN win7
    【随谈】designing the login page of our project
    【web】Ad in security code, making good use of resource
    SQL数据库内存设置篇
    关系数据库的查询优化策略
    利用SQL未公开的存储过程实现分页
    sql语句总结
    sql中使用cmd命令注销登录用户
    SQLServer 分页存储过程
  • 原文地址:https://www.cnblogs.com/zhansu/p/6106107.html
Copyright © 2011-2022 走看看