JS 的 Promise 看了好幾次了,大概清楚概念,但是一直心里沒底,也沒寫過代碼,今天把這些心虛的部分一波帶走。
Background
簡單聊一下背景,眾所周知 JavaScript 是以單線程的方式運行的。在某一時刻內(nèi)只能執(zhí)行特定的一個任務(wù),并且會阻塞其它任務(wù)的執(zhí)行。對于類似網(wǎng)絡(luò)請求等耗時的任務(wù),沒必要等任務(wù)執(zhí)行完后才繼續(xù)后面的操作。在這些任務(wù)完成前,JavaScript 完全可以往下執(zhí)行其他操作,當這些耗時的任務(wù)完成后則以回調(diào)的方式執(zhí)行相應(yīng)處理。這些就是 JavaScript 與生俱來的特性:異步與回調(diào)。
大概是這個樣子:
networkQuery(queryUrl, function(err, data){
if(err){
console.log("error!")
} else {
// handle data
}
})
當然這也帶了一個坑,如果你需要執(zhí)行多個耗時操作,并且他們之間存在執(zhí)行結(jié)果數(shù)據(jù)之間的依賴,那你不得不把代碼寫成下面這樣。回調(diào)地獄。
networkQuery(queryUrl, function(err, data){
if(err){
console.log("First query error!")
} else {
queryFromBackEnd(data.someUrl, function(err, data){
if(err){
console.log("Second query error!")
} else {
queryFromOtherSystem(data.otherUrl, function(err, data){
if(err){
console.log("Error!")
} else {
console.log("Finally get data in Callback Hell")
}
})
}
})
}
})
邏輯簡單還好說,稍微復(fù)雜一點,第二天根本看不懂自己寫了什么。
Promise
解決回調(diào)地獄的其中第一種方式就是使用 Promise。
Promise,如字面所說:承諾。
假裝是代碼:
我承諾(promise)今天要學(xué)會使用 Promise。然后(then)等到今天結(jié)束的時候,可能實現(xiàn)了承諾(resolve),不過也有可能遇到(catch)沒有實現(xiàn)承諾的情況(reject)。
翻譯一下:
let learnPromise = new Promise(function(resolve, reject){
// Wait until the end of the day.
// Check result: Do I understand how to use Promise?
let understand = false;
if(understand){
resolve("Absolutely!")
} else {
reject("Not yet")
}
})
learnPromise.then(function(messageFromResolve){
console.log("Understood ? " + messageFromResolve)
}).catch(function(messageFromReject){
console.log("Understood ? " + messageFromReject)
})
// OUTPUT
// Understood ? Not yet
第一行創(chuàng)建了一個 Promise 對象,創(chuàng)建的時候傳入了一個函數(shù),在函數(shù)內(nèi)部進行耗時操作。
此外函數(shù)接收兩個參數(shù),這兩個參數(shù)都是函數(shù)。分別是實現(xiàn)了承諾的后續(xù)操作函數(shù) resolve,和沒有實現(xiàn)承諾的后續(xù)操作函數(shù) reject。resolve 和 reject, 分別對應(yīng) 代碼的下半部分中 Promise 對象在調(diào)用 then() 時傳入的函數(shù),和調(diào)用 catch() 時傳入的函數(shù)。
注意Promise 對象 learnPromise 的使用方式,then() 和 catch(), Promise 讓編寫異步代碼稍微看起來像在寫同步代碼一樣。
Multiple Promises
回到最初的例子,回調(diào)地獄。如果使用 Promise,則每個 query 方法都不直接使用回調(diào)函數(shù),而是返回一個 Promise 對象,調(diào)用的時候在每一個 resolve 函數(shù)的最后調(diào)用下一階段的 query 函數(shù),返回對應(yīng)的 Promise 對象(也就是在 then 中傳入函數(shù)的最后返回一個 Promise 對象),就可以在 then 方法之后繼續(xù) 調(diào)用 then。
query()
.then( function(){ /* Return a Promise instance */})
.then( function(){ /* Return a Promise instance */})
.then( function(){ /* Return a Promise instance */})
.catch()
就像這樣,我們把返回的結(jié)果 message 也作為參數(shù)傳到了下一個 Promise,在最后一個 Promise 的 resolve 中打印。
let networkQuery = function(message){
return new Promise((resolve, reject) => {
let data = "First query result, based on " + message
resolve(data);
})
}
let queryFromBackEnd = function(message){
return new Promise((resolve, reject) => {
let data = "Second query result, based on " + message
resolve(data);
})
}
let queryFromOtherSystem = function(message){
return new Promise((resolve, reject) => {
let data = "Third query result, based on " + message
resolve(data);
})
}
networkQuery("nothing").then(tempData => {
return queryFromBackEnd(tempData)
}).then(data => {
return queryFromOtherSystem(data)
}).then(finalData => {
console.log(finalData)
})
// OUTPUT
// Third query result, based on Second query result, based on First query result, based on nothing
Other condition
當然有的時候我們或許并不關(guān)心執(zhí)行的過程,只關(guān)心是否成功的執(zhí)行完了所有任務(wù),也可以這樣使用
Promise.all([firstOperation(), secondOperation(), thirdOperation()]).then(() =>{
console.log("All finished")
// do something else
})
或者只要其中一個執(zhí)行成功就可以了:
Promise.race([firstOperation(), secondOperation(), thirdOperation()]).then(() =>{
console.log("One of them is finished")
// do something else
})
最后,understand = true。
好了,今天就到這里了。