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);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

成功了:没错
 
 
  • 1
  • 1

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

失败了:有错
成功了:undefined
 
 
  • 1
  • 2
  • 1
  • 2

然后我就傻眼了,搞什么啊,为什么明明失败了,成功的回调还会被调用呢? 
然后开始疯狂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);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

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

这个 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);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

结果变成了:

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

什么鬼?为什么改成这种写法以后又多出一个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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

失败了:有错
 
 
  • 1
  • 1

但是,问题是首先如果在之后再加入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);
    });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

结果为:

失败了:有错
 
 
  • 1
  • 1

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

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

至此已经基本明白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);
    })
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

失败了:有错
Uncaught (in promise) 有错
 
 
  • 1
  • 2
  • 1
  • 2

什么鸟,为什么还有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的功能是一致的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值