JS 的 Promise 看了好幾次了逝淹,大概清楚概念啄枕,但是一直心里沒(méi)底,也沒(méi)寫(xiě)過(guò)代碼蚓耽,今天把這些心虛的部分一波帶走渠牲。
Background
簡(jiǎn)單聊一下背景,眾所周知 JavaScript 是以單線程的方式運(yùn)行的步悠。在某一時(shí)刻內(nèi)只能執(zhí)行特定的一個(gè)任務(wù)签杈,并且會(huì)阻塞其它任務(wù)的執(zhí)行。對(duì)于類似網(wǎng)絡(luò)請(qǐng)求等耗時(shí)的任務(wù)鼎兽,沒(méi)必要等任務(wù)執(zhí)行完后才繼續(xù)后面的操作答姥。在這些任務(wù)完成前,JavaScript 完全可以往下執(zhí)行其他操作接奈,當(dāng)這些耗時(shí)的任務(wù)完成后則以回調(diào)的方式執(zhí)行相應(yīng)處理踢涌。這些就是 JavaScript 與生俱來(lái)的特性:異步與回調(diào)通孽。
大概是這個(gè)樣子:
networkQuery(queryUrl, function(err, data){
if(err){
console.log("error!")
} else {
// handle data
}
})
當(dāng)然這也帶了一個(gè)坑序宦,如果你需要執(zhí)行多個(gè)耗時(shí)操作,并且他們之間存在執(zhí)行結(jié)果數(shù)據(jù)之間的依賴背苦,那你不得不把代碼寫(xiě)成下面這樣互捌。回調(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")
}
})
}
})
}
})
邏輯簡(jiǎn)單還好說(shuō)行剂,稍微復(fù)雜一點(diǎn)秕噪,第二天根本看不懂自己寫(xiě)了什么。
Promise
解決回調(diào)地獄的其中第一種方式就是使用 Promise厚宰。
Promise腌巾,如字面所說(shuō):承諾。
假裝是代碼:
我承諾(promise)今天要學(xué)會(huì)使用 Promise铲觉。然后(then)等到今天結(jié)束的時(shí)候澈蝙,可能實(shí)現(xiàn)了承諾(resolve),不過(guò)也有可能遇到(catch)沒(méi)有實(shí)現(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)建了一個(gè) Promise 對(duì)象灯荧,創(chuàng)建的時(shí)候傳入了一個(gè)函數(shù),在函數(shù)內(nèi)部進(jìn)行耗時(shí)操作盐杂。
此外函數(shù)接收兩個(gè)參數(shù)逗载,這兩個(gè)參數(shù)都是函數(shù)。分別是實(shí)現(xiàn)了承諾的后續(xù)操作函數(shù) resolve
链烈,和沒(méi)有實(shí)現(xiàn)承諾的后續(xù)操作函數(shù) reject
厉斟。resolve
和 reject
, 分別對(duì)應(yīng) 代碼的下半部分中 Promise 對(duì)象在調(diào)用 then()
時(shí)傳入的函數(shù)强衡,和調(diào)用 catch()
時(shí)傳入的函數(shù)擦秽。
注意Promise 對(duì)象 learnPromise
的使用方式,then()
和 catch()
, Promise 讓編寫(xiě)異步代碼稍微看起來(lái)像在寫(xiě)同步代碼一樣号涯。
Multiple Promises
回到最初的例子目胡,回調(diào)地獄。如果使用 Promise链快,則每個(gè) query 方法都不直接使用回調(diào)函數(shù)誉己,而是返回一個(gè) Promise 對(duì)象,調(diào)用的時(shí)候在每一個(gè) resolve 函數(shù)的最后調(diào)用下一階段的 query 函數(shù)域蜗,返回對(duì)應(yīng)的 Promise 對(duì)象(也就是在 then
中傳入函數(shù)的最后返回一個(gè) Promise 對(duì)象)巨双,就可以在 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ù)傳到了下一個(gè) Promise筑累,在最后一個(gè) 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
當(dāng)然有的時(shí)候我們或許并不關(guān)心執(zhí)行的過(guò)程丝蹭,只關(guān)心是否成功的執(zhí)行完了所有任務(wù)慢宗,也可以這樣使用
Promise.all([firstOperation(), secondOperation(), thirdOperation()]).then(() =>{
console.log("All finished")
// do something else
})
或者只要其中一個(gè)執(zhí)行成功就可以了:
Promise.race([firstOperation(), secondOperation(), thirdOperation()]).then(() =>{
console.log("One of them is finished")
// do something else
})
最后,understand = true
奔穿。
好了镜沽,今天就到這里了。