本文譯自@Mohsan Riaz發表在medium的文章,已獲其受權,原文地址見傳送門javascript
在Javascript中編寫異步代碼經常使人不安,尤爲是在使用continuation-passing style(CPS)風格的時候。異步代碼影響代碼的可讀性,使代碼很難解耦爲更小的獨立代碼塊,致使其更易出錯,且之後很難改動。java
譯者注:continuation-passing style,簡單來講就是函數的返回不依靠return語句,而是依靠額外傳入一個回調函數,將返回值做爲回調函數的參數來傳出。具體可參考這篇博客編程
若是有一種能夠以相似同步代碼的風格來寫異步代碼的方式,那咱們的編程生涯確定會更加的愉快。使用JavaScript的promise能夠作到這一點。promise
讓咱們先開始嘗試找找使用callback有什麼問題。markdown
例如:若是須要獲取一個國家的列表,緊接着獲取國家列表中第一個國家的的城市列表,而後緊接着獲取這個城市列表中的全部大學,最終展現第一個城市的第一所大學。異步
因爲每次調用都相互依賴於上一次調用的結果,須要調用一系列的嵌套的回調函數。ide
function fetchCountries(){
fetchJSON("/countries", (success, countries) => {
if(success){
try{
// do some stuff
fetchCities(countries[0].id);
} catch(e){
processError();
}
}else
processError();
});
}
function fetchCities(countryId){
fetchJSON(`countries/${countryId}/cities`, (success, cities) => {
if(success){
try{
// do some stuff
fetchUniversities(cities[0].id);
} catch(e){
processError()
}
}else
processError();
});
}
function fetchUniversities(cityId){
fetchJSON(`cities/${cityId}/universities`, (success, universities) => {
if(success){
try{
// do some stuff
fetchUniversityDetails(universities[0].id);
}catch(e){
processError();
}
}else
processError();
});
}
function fetchUniversityDetails(univId){
fetchJSON(`/universities/${univId}`, (success, university) => {
if(success){
try{
// do some stuff
hideLoader();
}catch(e){
processError();
}
}else
processError();
});
}
複製代碼
上面這個實現有什麼問題?函數
回調函數沒有將異步邏輯放在同一個位置,而是決策接下來調用哪一個函數。簡而言之,咱們將控制流給了各個函數,讓他們緊密的耦合到了一塊兒。oop
在我看來,與簡單的回調處理(continuation-passing style)相比,promise有4個主要優勢:fetch
Javascript的Promise讓異步代碼像同步代碼同樣return一個值,這個返回值是個能承諾成功或失敗值的object。這個小改動讓它用起來很是強大。
get("/countries")
.then(populateContDropdown)
.then(countries => get(`countries/${countries[0].id}/cities`))
.then(populateCitiesDropdown)
.then(cities => get(`cities/${cities[0].id}/universities`))
.then(populateUnivDropdown)
.then(universities => get(`universities/${universities[0].id}`))
.then(populateUnivDetails)
.catch(showError)
.then(hideLoader)
複製代碼
與同步編程類似,一個函數的輸出是下一個函數的輸入,在調用鏈中像使用JSON.parse
同樣使用JS函數,他們的返回值會被餵給下一個回調函數。
相似JavaScript的try/catch
,若是有異常(exception),catch
函數會將其捕獲,後面的代碼會照常運行,上面例子中的loader會在執行後隱藏。
咱們在同一個地方定義全部異步邏輯,沒必要作額外的檢查或者爲錯誤處理使用try/catch
。最終,代碼具備更高的可讀性,更低的耦合度,可複用的獨立函數。
不管什麼時候出現有意或無心的異常,都會在下一個catch處理程序中拋出異常。你能夠將catch處理程序放置在鏈中的任何位置以捕獲特定的異常,此時能夠顯示一個錯誤或者從新取值。
get("/countries")
.then(JSON.parse)
.catch(e => console.log("Could not parse string"))
.then(...)
.catch(e => console.log("Error occured"))
複製代碼
在上面的例子中,第二個console
語句將不會被打印。
若是一個異常已被捕獲且想將其傳到下一個
catch
處理函數中,必須將其拋出。
若是想展現多個錯誤信息這樣就很方便,例如:
get("/coutries")
.then(...)
.catch(e => {
showLowLevelError();
throw e;
})
.catch(showHighLevelError)
複製代碼
若是一個事件但願能被屢次觸發,事件監聽器就很是適用,例如按鈕點擊事件。Promise和事件監聽器很類似,但他們有兩點本質的不一樣:
fullfiled
仍是rejected
。這頗有用,由於咱們對發生的結果作出反應更感興趣。舉個圖片預加載的簡單例子:
var imagePromise = preloadImage("src.png");
setTimeout(() => {
imagePromise.then(() => console.log("image was loaded") )
.catch(()=> console.log("could not load the image"))
}, 2000)
function preloadImage (path) {
return new Promise((resolve, reject) => {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
複製代碼
Promise.all([...])
對於批量決議頗有用
Promise.all([imagePromise1, imagePromise2, ....])
.then(...)
.catch(...)
複製代碼
在JavaScript中的大部分實踐中,Promise很是有用,尤爲是成功或者失敗回調函數只能被執行一次。但在一些實踐中,尤爲是事件回調函數會被屢次調用的實踐中,普通的回調函數更好。感謝你閱讀這篇文章,若是有什麼問題,請在評論區留言。
初次翻譯,若有問題歡迎指正。