您的位置:首页 > 其它

es6之promise被坑记

2016-11-25 10:23 239 查看
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的功能是一致的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: