es6之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同時又滿足我的需求,那就只能做一個比較繞的解決辦法,終極解決辦法就是