你有沒有在JavaScript中遇到過promises
並想知道它們是什麼?它們爲何會被稱爲promises
呢?它們是否和你以任何方式對另外一我的作出的承諾有關呢?javascript
此外,你爲何要使用promises
呢?與傳統的JavaScript操做回調(callbacks)相比,它們有什麼好處呢?前端
在本文中,你將學習有關JavaScript中promises
的全部內容。你將明白它們是什麼,怎麼去使用它們,以及爲何它們比回調更受歡迎。java
因此,promise是什麼?
promise是一個未來會返回值的對象。因爲這種將來
的東西,Promises很是適合異步JavaScript操做。git
若是你不明白異步JavaScript意味着什麼,你可能還不適合讀這篇文章。我建議你回到關於callbacks這篇文章瞭解後再回來。es6
經過類比會更好地解析JavaScript promise
的概念,因此咱們來這樣作(類比),使其概念更加清晰。github
想象一下,你準備下周爲你的侄女舉辦生日派對。當你談到派對時,你的朋友,Jeff,提出他能夠提供幫助。你很高心,讓他買一個黑森林(風格的)生日蛋糕。Jeff說能夠。數據庫
在這裏,Jeff告訴你他會給你買一個黑森林生日蛋糕。這是約定好的。在JavaScript中,promise
的工做方式和現實生活中的承諾同樣。可使用如下方式編寫JavaScript版本的場景:後端
// jeffBuysCake is a promise
const promise = jeffBuysCake('black forest')
複製代碼
你將學習如何構建
jeffBuysCake
。如今,把它當成一個promise
。數組
如今,Jeff還沒有采起行動。在JavaScript中,咱們說承諾(promise)正在等待中(pending)
。若是你console.log
一個promise
對象,就能夠驗證這點。promise
打印
jeffBuysCake
代表承諾正在等待中。
當咱們稍後一塊兒構建
jeffBuysCake
時,你將可以本身證實此console.log
語句。
在與Jeff交談以後,你開始計劃下一步。你意識到若是Jeff信守諾言,並在聚會時買來一個黑森林蛋糕,你就能夠按照計劃繼續派對了。
若是Jeff確實買來了蛋糕,在JavaScript中,咱們說這個promise是實現(resolved)
了。當一個承諾獲得實現時,你會在.then
調用中作下一件事情:
jeffBuysCake('black forest')
.then(partyAsPlanned) // Woohoo! 🎉🎉🎉
複製代碼
若是Jeff沒給你買來蛋糕,你必須本身去麪包店買了。(該死的,Jeff!)。若是發生這種狀況,咱們會說承諾被拒絕(rejected)
了。
當承諾被拒絕了,你能夠在.catch
調用中執行應急計劃。
jeffBuysCake('black forest')
.then(partyAsPlanned)
.catch(buyCakeYourself) // Grumble Grumble... #*$%
複製代碼
個人朋友,這就是對Promise
的剖析了。
在JavaScript中,咱們一般使用promises
來獲取或修改一條信息。當promise
獲得解決時,咱們會對返回的數據執行某些操做。當promise
拒絕時,咱們處理錯誤:
getSomethingWithPromise()
.then(data => {/* do something with data */})
.catch(err => {/* handle the error */})
複製代碼
如今,你知道一個promise
如何運做了。讓咱們進一步深刻研究如何構建一個promise
。
構建一個promise
你可使用new Promise
來建立一個promise。這個Promise構造函數是一個包含兩個參數 -- resolve
和reject
的函數。
const promise = new Promise((resolve, reject) => {
/* Do something here */
})
複製代碼
若是resolve
被調用,promise成功並繼續進入then
鏈式(操做)。你傳遞給resolve
的參數將是接下來then
調用中的參數:
const promise = new Promise((resolve, reject) => {
// Note: only 1 param allowed
return resolve(27)
})
// Parameter passed resolve would be the arguments passed into then.
promise.then(number => console.log(number)) // 27
複製代碼
若是reject
被調用,promise失敗並繼續進入catch
鏈式(操做)。一樣地,你傳遞給reject
的參數將是catch
調用中的參數:
const promise = new Promise((resolve, reject) => {
// Note: only 1 param allowed
return reject('💩💩💩')
})
// Parameter passed into reject would be the arguments passed into catch.
promise.catch(err => console.log(err)) // 💩💩💩
複製代碼
你能看出
resolve
和reject
都是回調函數嗎?😉
讓咱們練習一下,嘗試構建jeffBuysCake
promise。
首先,你知道Jeff說他會買一個蛋糕。那就是一個承諾。因此,咱們從空promise入手:
const jeffBuysCake = cakeType => {
return new Promise((resolve, reject) => {
// Do something here
})
}
複製代碼
接下來,Jeff說他將在一週內購買蛋糕。讓咱們使用setTimeout
函數模擬這個等待七天的時間。咱們將等待一秒,而不是七天:
const jeffBuysCake = cakeType => {
return new Promise((resolve, reject) => {
setTimeout(()=> {
// Checks if Jeff buys a black forest cake
}, 1000)
})
}
複製代碼
若是Jeff在一秒以後買了個黑森林蛋糕,咱們就會返回promise,而後將黑森林蛋糕傳遞給then
。
若是Jeff買了另外一種類型的蛋糕,咱們拒接這個promise,而且說no cake
,這會致使promise進入catch
調用。
const jeffBuysCake = cakeType => {
return new Promise((resolve, reject) => {
setTimeout(()=> {
if (cakeType
- = 'black forest') {
resolve('black forest cake!')
} else {
reject('No cake 😢')
}
}, 1000)
})
}
複製代碼
讓咱們來測試下這個promise。當你在下面的console.log
記錄時,你會看到promise正在pedding(等待)
。(若是你當即檢查控制檯,狀態將只是暫時掛起狀態。若是你須要更多時間檢查控制檯,請隨時將超時時間延長至10秒)。
const promise = jeffBuysCake('black forest')
console.log(promise)
複製代碼
打印
jeffBuysCake
代表承諾正在等待中。
若是你在promise鏈式中添加then
和catch
,你會看到black forest cake!
或no cake 😢
信息,這取決於你傳入jeffBuysCake
的蛋糕類型。
const promise = jeffBuysCake('black forest')
.then(cake => console.log(cake))
.catch(nocake => console.log(nocake))
複製代碼
打印出來是「黑森林蛋糕」仍是「沒有蛋糕」的信息,取決於你傳入
jeffBuysCake
的(參數)。
建立一個promise不是很難,是吧?😉
既然你知道什麼是promise,如何製做一個promise以及如何使用promise。那麼,咱們來回答下一個問題 -- 在異步JavaScript中爲何要使用promise而不是回調呢?
Promises vs Callbacks
開發人員更喜歡promises而不是callbacks有三個緣由:
- Promise減小了嵌套代碼的數量
- Promise容許你輕鬆地可視化執行流程
- Promise讓你能夠在鏈式的末尾去處理全部錯誤
爲了看到這三個好處,讓咱們編寫一些JavaScript代碼,它們經過callbacks
和promises
來作一些異步事情。
對於這個過程,假設你正在運營一個在線商店。你須要在客戶購買東西時向他收費,而後將他們的信息輸入到你的數據庫中。最後,你將向他們發送電子郵件:
- 向客戶收費
- 將客戶信息輸入到數據庫
- 發送電子郵件給客戶
讓咱們一步一步地解決。首先,你須要一種從前端到後端獲取信息的方法。一般,你會對這些操做使用post
請求。
若是你使用Express
或Node
,則初始化代碼可能以下所示。若是你不知道任何Node
或Express
(的知識點),請不要擔憂。它們不是本文的主要部分。跟着下面來走:
// A little bit of NodeJS here. This is how you'll get data from the frontend through your API.
app.post('/buy-thing', (req, res) => {
const customer = req.body
// Charge customer here
})
複製代碼
讓咱們先介紹一下基於callback
的代碼。在這裏,你想要向客戶收費。若是收費成功,則將其信息添加到數據庫中。若是收費失敗,則會拋出錯誤,所以你的服務器能夠處理錯誤。
代碼以下所示:
// Callback based code
app.post('/buy-thing', (req, res) => {
const customer = req.body
// First operation: charge the customer
chargeCustomer(customer, (err, charge) => {
if (err) throw err
// Add to database here
})
})
複製代碼
如今,讓咱們切換到基於promise
的代碼。一樣地,你向客戶收費。若是收費成功,則經過調用then
將其信息添加到數據庫中。若是收費失敗,你將在catch
調用中自動處理:
// Promised based code
app.post('/buy-thing', (req, res) => {
const customer = req.body
// First operation: charge the customer
chargeCustomer(customer)
.then(/* Add to database */)
.catch(err => console.log(err))
})
複製代碼
繼續,你能夠在收費成功後將你的客戶信息添加到數據庫中。若是數據庫操做成功,則會向客戶發送電子郵件。不然,你會拋出一個錯誤。
考慮到這些步驟,基於callback
的代碼以下:
// Callback based code
app.post('/buy-thing', (req, res) => {
const customer = req.body
chargeCustomer(customer, (err, charge) => {
if (err) throw err
// Second operation: Add to database
addToDatabase(customer, (err, document) => {
if (err) throw err
// Send email here
})
})
})
複製代碼
對於基於promise
的代碼,若是數據庫操做成功,則在下一個then
調用時發送電子郵件。若是數據庫操做失敗,則會在最終的catch
語句中自動處理錯誤:
// Promised based code
app.post('/buy-thing', (req, res) => {
const customer = req.body
chargeCustomer(customer)
// Second operation: Add to database
.then(_ => addToDatabase(customer))
.then(/* Send email */)
.catch(err => console.log(err))
})
複製代碼
繼續最後一步,在數據庫操做成功時向客戶發送電子郵件。若是成功發送此電子郵件,則會有成功消息通知到你的前端。不然,你拋出一個錯誤:
如下是基於callback
的代碼:
app.post('/buy-thing', (req, res) => {
const customer = req.body
chargeCustomer(customer, (err, charge) => {
if (err) throw err
addToDatabase(customer, (err, document) => {
if (err) throw err
sendEmail(customer, (err, result) => {
if (err) throw err
// Tells frontend success message.
res.send('success!')
})
})
})
})
複製代碼
而後,如下基於promise
的代碼:
app.post('/buy-thing', (req, res) => {
const customer = req.body
chargeCustomer(customer)
.then(_ => addToDatabase(customer))
.then(_ => sendEmail(customer) )
.then(result => res.send('success!')))
.catch(err => console.log(err))
})
複製代碼
看看爲何使用promises
而不是callbacks
編寫異步代碼要容易得多?你從回調地獄(callback hell)一會兒切換到了鏈式樂土上😂。
一次觸發多個promises
promises
比callbacks
的另外一個好處是,若是操做不依賴於彼此,則能夠同時觸發兩個(或多個)promises,可是執行第三個操做須要兩個結果。
爲此,你使用Promise.all
方法,而後傳入一組你想要等待的promises。then
的參數將會是一個數組,其包含你promises返回的結果。
const friesPromise = getFries()
const burgerPromise = getBurger()
const drinksPromise = getDrinks()
const eatMeal = Promise.all([
friesPromise,
burgerPromise,
drinksPromise
])
.then([fries, burger, drinks] => {
console.log(`Chomp. Awesome ${burger}! 🍔`)
console.log(`Chomp. Delicious ${fries}! 🍟`)
console.log(`Slurp. Ugh, shitty drink ${drink} 🤢 `)
})
複製代碼
備註:還有一個名爲
Promise.race
的方法,但我還沒找到合適的用例。你能夠點擊這裏去查看。
最後,咱們來談談瀏覽器支持狀況!若是你不能在生產環境中使用它,那爲何要學習promises
呢。是吧?
瀏覽器支持Promise
使人興奮的消息是:全部主流瀏覽器都支持promises!
若是你須要支持IE 11
及其如下版本,你可使用Taylor Hakes製做的Promise Polyfill。它支持IE8的promises。😮
結語
你在本文中學到了全部關於promises
的知識。簡而言之,promises
棒極了。它能夠幫助你編寫異步代碼,而無需進入回調地獄。
儘管你可能但願不管何時都使用promises
,但有些狀況callbacks
也是有意義的。不要忘記了callbacks
啊😉。
若是你有疑問,請隨時在下面發表評論,我會盡快回復你的。【PS:本文譯文,若需做者解答疑問,請移步原做者文章下評論】
感謝閱讀。這篇文章是否幫助到你?若是有,我但願你考慮分享它。你可能會幫助到其餘人。很是感謝!
後話
下一篇關於 async/await
本文同步分享在 博客「Jimmy」(JueJin)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。