es6之promise被坑记

怒了,新浪微博也太坑了,代码片段太麻烦了。

promise的介绍就不多说了。
几个网址:
http://es6.ruanyifeng.com/#docs/promise
http://www.html5rocks.com/zh/tutorials/es6/promises/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

接下来看怎么被坑的。
先说说被坑的原因,主要是以前一直以为jQuery的deferred对象和promise是一样的api,所以一直没有深入研究,用jQuery也用习惯了,就一直放弃了promise的深入研究。

事实证明,浅尝辄止是可耻的,也是最容易被坑的。

代码1如下。

    var error = true;
    function test(){
        //promise处理异步函数的回调,这里简单的用一个error代表正确结果和非预期结果的判断条件
        var promise = new Promise(function(resolve,reject){
            setTimeout(function(){
                if(!error){
                    resolve("没错");
                }else{
                    reject("有错");
                }
            },100)
        });
        //test函数内部处理非预期结果。
        return promise.catch(function(error){
            console.log("失败了:"+error);
        });
    }
    test().then(function(str){
        console.log("成功了:"+str);
    });

当error为false的时候,输出结果

成功了:没错

当error为true的时候,输出结果

失败了:有错
成功了:undefined

然后我就傻眼了,搞什么啊,为什么明明失败了,成功的回调还会被调用呢?
然后开始疯狂google。

看到一个答案是:

当catch函数里没有抛出新的error时,promise认为错误已经被解决。

天真的我以为找到了问题的根源,于是简单的改成代码2

    var error = true;
    function test(){
        var promise = new Promise(function(resolve,reject){
            setTimeout(function(){
                if(!error){
                    resolve("没错");
                }else{
                    reject("有错");
                }
            },100)
        });
        return promise.catch(function(error){
            //没错,就是改了这里而已
            throw new Error("失败了:"+error)
        });
    }
    test().then(function(str){
        console.log("成功了:"+str);
    });

然后重新运行,结果变成了

Uncaught (in promise) Error: 失败了:有错

这个 Uncaught是个什么鬼?明明前面不是要抛出异常么?为什么又来个没有catch?
于是我开始怀疑我的写法的问题。
又开始改成代码3:

    var error = true;
    function test(){
        var promise = new Promise(function(resolve,reject){
            setTimeout(function(){
                if(!error){
                    resolve("没错");
                }else{
                    reject("有错");
                }
            },100)
        });
        promise.catch(function(error){
            throw new Error("失败了:"+error)
        });
        return promise;
    }
    test().then(function(str){
        console.log("成功了:"+str);
    });

结果变成了:

Uncaught (in promise) Error: 失败了:有错
Uncaught (in promise) 有错

什么鬼?为什么改成这种写法以后又多出一个Uncaught。
此刻的我彻底惊呆了。开始怀疑自己的世界观了,认为自己到底是有多不小心,肯定哪里哪里的写法写错了,然后重复看上面的三篇基础介绍里的例子,对照自己的写法,始终没发现问题。

到了这一步的时候,我彻底凌乱了,一度觉得这个promise是什么鬼东西,还不如jquery的deferred呢。手放在删除键上犹豫了很久要不要改回jquery的deferred对象。

还好最后我忍住了冲动,重新认真的看了介绍。
总算明白了原因。

Promise是一种链式的回调,每一个then或者catch返回的是一个已经处理过的promise对象,同时catch只能catch之前链条中出现的错误。

那结合上面的那个说明,也就是说,我的catch函数因为是写在test函数内部的,所以它是这个链式回调的第一个环节,它只能catch promise的executor里的reject的分支,代码1中因为catch了reject分支,所以promise认为错误已经被处理了,自然会继续调用then的回调。
而代码2中,因为在catch中重新抛出了错误,而在这个catch之后的then里没有做错误的处理,于是出现了Uncaught的错误提示。
在代码3中,改了写法,return的是初始promise对象,所以实际上catch和then都属于第一链,在catch中因为抛出了新的error,故而出现一个Uncaught提示,而在then中没有定义reject的callback,且在then之后没有添加catch callback,所以会抛出第二个Uncaught提示。

问题总算找到了。
于是正确代码应该是

    var error = true;
     function test(){
        var promise = new Promise(function(resolve,reject){
            setTimeout(function(){
                if(!error){
                    resolve("没错");
                }else{
                    reject("有错");
                }
            },100)
        });
        return promise
    }
    test().then(function(str){
        console.log("成功了:"+str);
    }).catch(function(error){
        console.log("失败了:"+error)
    });

也就是将代码1中的catch移到then的后面即可。运行结果也就成为了预期结果

失败了:有错

但是,问题是首先如果在之后再加入catch callback,根据前面的理论,下一个catch因为前面的reject已经被处理,所以第二个catch应该是不会运行的,那是不是意味着错误的回调只能写一次呢?
改成代码4

    var error = true;
    function test(){
        var promise = new Promise(function(resolve,reject){
            setTimeout(function(){
                if(!error){
                    resolve("没错");
                }else{
                    reject("有错");
                }
            },100)
        });
        return promise
    }
    test().then(function(str){
        console.log("成功了:"+str);
    }).catch(function(error){
        console.log("失败了:"+error)
    }).catch(function(error){
        console.log("第二个回调:"+error);
    });

结果为:

失败了:有错

果然,第二个catch没有运行。
那如果我在这个之后再加入一个then呢?会有什么样的结果?
代码5

失败了:有错
第二个成功回调:有错

至此已经基本明白promise的链式规则了。
1、每一个回调都接受上一个响应过的回调的返回值作为参数,代码5中,因为响应的是第一个catch callback,所以处理完错误后响应了第二个then的回调,而第二个then的参数则为第一个catch callback的返回值。如果error为false的话,第二个then的参数则变成了第一个then的返回值。
2、catch函数只能catch在链条之前发生的reject,同时,浏览器的错误也会被认为是reject状态,且reject的内容为错误提示。

这两点jQuery的deferred是和promise的实现方法是不同的。
jQuery的deferred的fail函数是有错误发生的时候就会响应,无论写在链式的什么位置。且,每一个callback的参数都为最初的resolve或者reject的值。

现在,回到最初我想解决的问题,如果我想要在test函数里统一处理reject,而test函数之外,只接受resolve的状态怎么实现?

要实现这个,其实也很简单,只需要保证catch不是在then之后就可以了,也就是将代码2里的throw部分改成处理错误既可。于是改成代码6

    var error = true;
    function test(){
        var promise = new Promise(function(resolve,reject){
            setTimeout(function(){
                if(!error){
                    resolve("没错");
                }else{
                    reject("有错");
                }
            },100)
        });
        promise.catch(function(error){
            console.log("失败了:"+error)
            return error;
        })
        return promise;
    }
    test().then(function(str){
        console.log("成功了:"+str);
    })

当我得意洋洋以为理解透彻了的时候,结果又让我吓了一跳

失败了:有错
Uncaught (in promise) 有错

什么鸟,为什么还有Uncaught的提示?
冷静想想,首先,Uncaught的意思是代表有reject状态没有被catch,那没有被catch的地方只有可能是在test函数外的then后面。按照前面的理论,代码6的写法里,catch完以后我返回了原本的promise对象,而原本的promise对象里reject的状态其实是没有catch的。所以才会出现Uncaught的提示。

这样一来,好吧,只能丢弃jQuery的deferred的影响。
如果要在test里处理错误,就只能在异步的函数里进行处理,也就是将setTimeout的reject去掉,只留下resolve,然后每一个then都处理resolve,并且返回直接将参数作为返回值。

但是这样一来,在test外就没有办法再次调用错误回调了。

好吧,看来是我的要求比较绕,要求本身是和jQuery的deferred的功能是一致的。

但是,为了不使用jQuery同时又满足我的需求,那就只能做一个比较绕的解决办法,终极解决办法就是

你可能感兴趣的:(es6之promise被坑记)