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

    这是小弟的一篇开篇小作,如有不当之处,请各位道友批评指正。本文将探讨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的实现有了进一步认识。最后希望自己能够坚持写博客,在写作中学习。
    

    参考文献:

  • 相关阅读:
    Python获取Linux的家目录
    Python 批量安装包、查看当前程序依赖的包
    获取linux目录下最新的文件
    Linux破解navicat
    Linux添加PATH
    Linux下文件分析 | 命令行
    ROP | 蒸米 -x86
    Jarvis OJ | guess
    杂项入门
    Whale ctf | misc
  • 原文地址:https://www.cnblogs.com/floor/p/6648045.html
Copyright © 2011-2022 走看看