我們在 Day5 提到了 JS 有非同步的機制,也因為這個機制,所以不會讓同步執行的 JS 阻塞,遇到 AJAX
、 timer
等等的操作,都會先丟到事件佇列中,然後再慢慢拉回執行。
但是當 callback function 一複雜起來,將會導致回調地域
,也就是 callback function 中包了更多的 callback function,原意是想等待前面一件事情完成後,再接著做新的事情,但卻導致程式碼變成很深的巢狀,也喪失了可讀性與不好維護。
Promise
優化了非同步執行的流程語法,使用上也很簡單,
Promise
,處理 成功
與 失敗
狀態then
串接catch
處理錯誤(失敗)then
和 catch
的用法在昨日 Day22 已經展示,但上方第一點提到的成功
與失敗
是什麼意思呢?
Promise
顧名思義就是「承諾」的意思,既然是承諾,必定會告知對方結果,像是某A先生與網美小花告白,小花是個有禮貌的人,不會直接無聲卡,所以她一定會回應某A先生,不論這個回應是好消息還是壞消息,而且這個狀態一旦回應,就不能再更改,也就是說網美小花只要接受某A先生,就不能夠反悔。
寫成程式碼大概就長這樣
var willTogether = function(money) {
return new Promise(function (resolve, reject) {
if(money > 100) {
resolve("我也愛你,A先生");
} else {
reject(new Error('你是一個好人,但你值得更好的人'));
}
});
}
willTogether(10)
.then(function(val) {
console.log(val);
})
.catch(function(err) {
console.log(err);
});
從上方的範例中可以得知,Promise
總共有三個狀態
pending
: 等待中、還未給出回應。resolve
: 完成/成功reject
: 失敗隔日某A先生撿到一些錢後...(純屬玩笑,請勿認真Q_Q)
好啦,總之就是 then
會接收 resolve
的 value,catch
則處理 reject
狀態。
而每個 then
可以再回傳一個 Promise
(或值),它的結果會傳入下一個 then
,所以可以一直串下去,也不會寫成醜醜的回調地域
。
但是使用上需要注意,catch
如果是串在最後面,只要中途發生了 reject
,將導致後面的 then
不會被執行,像是這樣:
willTogether(500)
.then(function(val) {
console.log("A1", val); // A1 正常印出
return willTogether(50);
})
.then(function(val) {
console.log("A2", val); // A2 不會印出
return willTogether(300);
})
.then(function(val) {
console.log("A3", val); // A3 不會印出
return willTogether(200);
})
.then(function(val){
console.log("A4", val); // A4 不會印出
})
.catch(function(err) {
console.log(err); // sorry
});
A1
如期印出,但是 A2
、A3
、A4
則不會印出,因為 willTogether(50)
回傳的狀態是 reject
,所以直接被送到離它最近的 catch
處理,而 A2
、A3
、A4
也就不會被執行了。
如果改成這樣:
willTogether(500)
.then(function(val) {
console.log("A1", val); // A1 正常印出
return willTogether(50);
})
.catch(function(err) {
console.log(err); // sorry
return "keep going";
})
.then(function(val) {
console.log("A2", val); // A2 keep going
return willTogether(300);
})
.then(function(val) {
console.log("A3", val); // A3 正常印出
return willTogether(200);
})
.then(function(val){
console.log("A4", val); // A4 正常印出
})
.catch(function(err) {
console.log(err); // 不會印出
});
多在 willTogether(50)
串一個 catch
來收它的錯誤,則在印出錯誤訊息後,回傳 keep going 字串,繼續 then
執行下去,所以 A2
印出 keep going
(不一定要回傳,不回傳下一個 then
會收到 undefined
),之後的 A3、A4
則正常印出。
其實 catch
只是 then
的縮寫(語法糖)而已,then
完整寫法應該是這樣:
.then(function(val){
// if resolve
...
}, function(err){
// if reject
...
})
也就是其實 then
自己就可以處理 reject
,不需要 catch
,只是平常直接省略後面接收 reject
的函數,因為在沒有處理 reject
的情況下,每個 then
都寫出兩個函數來會讓程式碼很亂。
而 catch
則是 then
縮寫成這樣:
.then(undefined, function(err){
// if reject
...
})
那我們把上面的程式碼改一下,把 A2
所在的那個 then
多加一個 reject function
,
willTogether(500)
.then(function(val) {
console.log("A1", val); // A1 正常印出
return willTogether(50);
})
.then(function(val) {
console.log("A2", val); // A2 不會印出
return willTogether(300); // 也不會回傳
}, function(err){
console.log(err); // sorry
return "keep going"; // 這個才會回傳
})
.then(function(val) {
console.log("A3", val); // A3 keep going
return willTogether(200);
})
.then(function(val){
console.log("A4", val); // A4 正常印出
})
.catch(function(err) {
console.log(err); // 不會印出
});
我們可以看到 A2
不會印出,因為前一個 Promise
回傳的狀態是 reject
,所以 A2
這個 then
接收到後,是執行第二個 reject
那個函數,也就是印出錯誤訊息後,再回傳一個 keep going 的字串。
resolve
不一定要寫入回傳值,也可以 resolve()
回傳 undefined
。
只要回傳的狀態不是 reject
,就算當前是負責處理 reject
的 callback function,它 return 的值一樣會進入下一個 then
的 resolve function
中,所以範例中的 reject function
,它回傳的 keep going 字串才會繼續被傳下去,而沒有進入下一個 then
的 reject function
。
範例中網美小花總是很快回應,所以看不出 Promise
的好處。如果今天的狀況是:
「網美小花對於每個追求者有不固定的思考時間,等待思考結束後才會給回應(接受、拒絕),但是追求者也因為很有禮貌,所以會等網美小花回應前一個追求者後,才會進行追求」
這樣的狀況,就複雜了很多。
還不會 Promise
之前的你,是不是準備在 callback function 中寫更多的 callback function 來處理每個追求者呢?
至此有沒有感受到 Promise
的力量了XD
Promise
的重點在於將非同步的 function 執行流程化,藉由then
來順序的執行,catch
對於錯誤的處理機制也更方便。
其實在簡單的情況下,可以不用建立一個完整的 Promise
,可以使用
Promise.resolve
Promise.reject
像是上方的範例可以改寫成,
var willTogether = function(money) {
if(money > 100) {
return Promise.resolve("我也愛你,A先生");
} else {
return Promise.reject(new Error('你是一個好人,但你值得更好的人'));
}
}
恭喜你已經會了 Promise
基本的用法,但是還沒完,明天會再講講 race、all、async、await
,加油!
文章盡量使用淺白的方式來介紹,如果導致專有名詞表達不是那麼精準,還請包容,也可以留言幫忙補充,學習的路上有你有我~
今日的分享就到這,我們明天見