zoukankan      html  css  js  c++  java
  • 【转】实现一个自己的promise


    转, 原文: http://blog.csdn.net/yibingxiong1/article/details/68075416
    -------------------------------------------
    这是小弟的一篇开篇小作,如有不当之处,请各位道友批评指正。本文将探讨Promise的实现。

    一、ES6中的Promise

    1、简介

    据说js很早就实现了Promise,我是不知道的,我第一次接触Promise就是在ES6中。Promise就是规定在未来达到某个状态时应该采取某种行动,而这种未来的状态是不确定的。阮一峰说Promise对象用来传递异步消息,代表了某个未来才会知道结果的事件,并为这个事件提供统一的API,供进一步处理。
    Promise和事件有本质区别:Promise是用一次就扔,而事件可以被多次触发;Promise必定会产生一个信号,要么resolved(fulfilled),要么rejected,而事件可能不被触发。
    

    2、基本用法

    var p = new Promise(function(resolve){
        setTimeout(function(){
            resolve(111);
        },1000)
    });
    
    p.then(function(value){
        console.log("承诺解决了,拿到的数据为:"+value);
    });
    上面创建了一个promise,1秒后解决,然后用then方法添加了状态改变的回调函数。then方法中可以指定两个函数,第一个位承诺是变成resolved状态的回调函数,第二个是承诺变为rejected状态的回调函数(可省略)。也可以在catch方法中指定承诺变为rejected的函数。如下:
    
    p.catch(function(error){
        console.log("rejected callback");
    })

    3、Promise.all()的用法

    它接受一个promise对象数组为参数,当数组中的每一个promise都变成resolved状态时,整个才算为resolved,数组中promise的返回值将组成一个数组传递给Promise.all()返回的promise的回调函数。数组中的只要有一个为rejected整个为rejected;
    
    var p1 = new Promise(function(resolve){
            resolve("p1 resolved");
    });
    var p2 = new Promise(function(resolve){
            resolve('p2 resolved');
    });
    var p3 = new Promise(function(resolve){
            resolve("p3 resolved");
    });
    
    
    var p = Promise.all([p1,p2,p3]);//这里得到了一个新的promise
    p.then(function(value){
        console.log("p resolved, 得到的参数为:"+value);
    })
    .catch(function(error){
        console.log("p rejected,"+error);
    });
    //output:p resolved, 得到的参数为:p1 resolved,p2 resolved,p3 resolved
    将p2改为rejected
    
    var p1 = new Promise(function(resolve){
            resolve("p1 resolved");
    });
    var p2 = new Promise(function(resolve,reject){
            reject('p2 rejected');
    });
    var p3 = new Promise(function(resolve){
            resolve("p3 resolved");
    });
    
    
    p = Promise.all([p1,p2,p3]);
    p.then(function(value){
        console.log("p resolved, 得到的参数为:"+value);
    })
    .catch(function(error){
        console.log("p rejected,"+error);
    });
    //output:p rejected,p2 rejected

    4、Promise.race()的用法

    它和Promise.all()的参数相同,它返回的promise的状态随着promise数组中第一个状态发生改变的promise的状态而改变。
    
    var p1 = new Promise(function(resolve){
            resolve("p1 resolved");
    });
    var p2 = new Promise(function(resolve,reject){
            reject('p2 rejected');
    });
    var p3 = new Promise(function(resolve){
            resolve("p3 resolved");
    });
    
    
    p = Promise.race([p1,p2,p3]);
    p.then(function(value){
        console.log("p resolved, 得到的参数为:"+value);
    })
    .catch(function(error){
        console.log("p rejected,"+error);
    });
    //output:p resolved, 得到的参数为:p1 resolved
    我们发现虽然p2是rejected,但是p是resolved,因为第一个状态变化的是p1,而p1是resolved。如果p1是rejected,那么p一定是rejected。
    上面就是ES6原生支持的Promise,那么我们该如何实现一个类似的自己的Promise呢?都原生支持了为什么还要自己实现呢?第一它是一种乐趣。第二它可以帮助我们更好的认识发布订阅模式。下面开始正式战斗!(有木有很激动,终于要开始了)。
    

    二、实现自己的Promise

    1、基础实现

    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);
    }
    这个构造函数传进来一个function,声明了俩个内部变量,value传到其内部方法resolve,defferreds存储回调函数,每次调用实例的then方法会让then中的函数入队,其内部方法resolve接受一个参数,它遍历了defferreds队列,并执行其中的方法。这个内部方法被传递给了构造函数的参数fn。结合ES6中Promise的基本用法应该不难理解这段代码。其then方法相当于是一个订阅过程,resolve方法相当于一个发布过程。
    
    var mypromise  = new Promise(function(resolve){
        setTimeout(function(){
            resolve("qqq");
        },1000)
    });
    
    mypromise.then(function(value){
        console.log(value); //qqq
    })

    2、问题修复

    上述Promise的问题是,如果传进去的不是一个异步函数,那么resolve方法会先执行,此时还没有调用then,也就是说还没有人订阅,defferreds队列还是空的,不合预期。改进如下:
    
    function Promise(fn) {
        var value = null,
            deferreds = [];
    
        this.then = function (onFulfilled) {
            deferreds.push(onFulfilled);
        };
    
        function resolve(value) {
        //这里将resolve放到了栈底,所以then会先执行(如果有then的话)
            setTimeout(function () {    
                deferreds.forEach(function (deferred) {
                    deferred(value);
                });
            }, 0);
        }
        fn(resolve);
    }

    3、考虑一种情况:如果回调函数注册的很晚会怎么样

    var mypromise  = new Promise(function(resolve){
            resolve("qqq");
    });
    setTimeout(function(){
       mypromise.then(function(value){
        console.log(value);
    }) 
    },1000);
    结果是啥也没做,为什么呢?因为当我们注册回调的时候resolve已经执行了。那可咋整呢?解决方法就是记住Promise的状态。请看代码:
    
    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;
            state = 'fulfilled';
            setTimeout(function () {
                deferreds.forEach(function (deferred) {
                    deferred(value);
                });
            }, 0);
        }
    
        fn(resolve);
    }
    就是这么简单,我们为Promise增加了一个内部变量state保存其状态,初始为pending(等待的意思),当resolve时,改变其状态为fulfilled,调then的时候做个判断,如果是pending说明resolve方法还没执行,那么我们将回调函数加到队列等待resolve即可,如果是fulfilled,说明resolve已经执行,那么我们直接执行新加入的回调函数。至于那个return this,如果你用过jquery就知道了。
    

    4、Promise链

    我们考虑这样的情况
    
    var mypromise  = new Promise(function(resolve){
            resolve("first promise");
    });
    mypromise.then(function(value){
        console.log(value);
        return new Promise(function(resolve){
            resolve("second promise");
        })
    })
    .then(function(v){
        console.log(v);
    }) 
    //结果:first promise
    //     first promise
    //为啥是两个first promise,这个是必然的,好好想想。第一个then返回的是前一个promise,而不是新创建的promise。
    
    下面做一件有难度的是,我们来实现promise链,我看了几个小时才搞明白。不要怕,打起精神来,我们开始。
    
    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);
    }
    
    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 () {
            deferreds.forEach(function (deferred) {
                handle(deferred);
            });
        }, 0);
    }
    then方法中返回了一个新的promise实例,在新实例中调了一个内部方法,传进去一个回调函数和一个resolve方法构成的对象;内部方法判断如果当前promise没有调resolve的话,将传入的对象入队,否则的话直接调传入的回调函数,将回调函的返回值交给resolove方法。想一下,我们的回调函数会返回什么值,可能是一个promise对象也可能是字符串或者undefined。如果说返回了一个对象,证明用户又返回了一个promise,此时我们将用户返回的promise的then拿到,然后调这个then,如果不是个对象或者函数,证明用户没有返回新的promise,此时直接resolve。
    

    5、拒绝状态

    前面讲了resolve,reject就是手到禽来了。
    
    function Promise(fn) {
        var state = 'pending',
            value = null,
            deferreds = [];
    
        this.then = function (onFulfilled, onRejected) {
            return new Promise(function (resolve, reject) {
                handle({
                    onFulfilled: onFulfilled || null,
                    onRejected: onRejected || null,
                    resolve: resolve,
                    reject: reject
                });
            });
        };
    
        function handle(deferred) {
            if (state === 'pending') {
                deferreds.push(deferred);
                return;
            }
    
            var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
                ret;
            if (cb === null) {
                cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
                cb(value);
                return;
            }
            ret = cb(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, reject);
                    return;
                }
            }
            state = 'fulfilled';
            value = newValue;
            finale();
        }
    
        function reject(reason) {
            state = 'rejected';
            value = reason;
            finale();
        }
    
        function finale() {
            setTimeout(function () {
                deferreds.forEach(function (deferred) {
                    handle(deferred);
                });
            }, 0);
        }
    
        fn(resolve, reject);
    }

    6、处理resolve和reject的回调函数异常

    //改造了内部函数handle
    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }
    
        var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
            ret;
        if (cb === null) {
            cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
            cb(value);
            return;
        }
        try {
            ret = cb(value);
            deferred.resolve(ret);
        } catch (e) {
            deferred.reject(e);
        } 
    }
    
     真是无语,我已经写完了,Promise.all()和Promise.race()的实现和总结都写好了。已为添加参考文献是那个引用,按了快捷键Ctrl+Q,结果退出了浏览器,气死了。不写了,大家可以参考文章最后的参考文献,不过那个race方法的实现可能还有点问题,返回的promise的状态不是跟第一个数组中状态发生改变的promise的状态一致,而是最后一个。
    

    三、总结

    第一次写博客,突然那些写了那么多优秀文章的作者表示由衷佩服。这个Promise的实现确实是非常巧妙的,如果真的要我独自实现,恐怕还需要一些时日。不管怎样,终于完成了我的开篇之作。写作过程中对Promise的实现有了进一步认识。最后希望自己能够坚持写博客,在写作中学习。
    

    参考文献:

  • 相关阅读:
    Mybatis 原始dao CRUD方法
    JQuery的焦点事件focus() 与按键事件keydown() 及js判断当前页面是否为顶级页面 子页面刷新将顶级页面刷新 window.top.location
    使用actionerror做失败登录验证
    Java项目中的下载 与 上传
    shiro框架 4种授权方式 说明
    javascript 中数组的创建 添加 与将数组转换成字符串 页面三种提交请求的方式
    序列化表单为json对象,datagrid带额外参提交一次查询 后台用Spring data JPA 实现带条件的分页查询 多表关联查询
    Spring data JPA 理解(默认查询 自定义查询 分页查询)及no session 三种处理方法
    orcal 数据库 maven架构 ssh框架 的全注解环境模版 maven中央仓库批量删除lastupdated文件后依然是lastupdated解决方法 mirror aliyun中央仓库
    EasyUI加zTree使用解析 easyui修改操作的表单回显方法 验证框提交表单前验证 datagrid的load方法
  • 原文地址:https://www.cnblogs.com/oxspirt/p/7838922.html
Copyright © 2011-2022 走看看