JavaScript非同步與Promise
Promise解決的問題
相信每個前端都遇到過這樣一個問題,當一個非同步任務的執行需要依賴另一個非同步任務的結果時,我們一般會將兩個非同步任務巢狀起來,這種情況發生一兩次還可以忍,但是發生很多次之後,就形成了所謂的回撥地獄,程式碼層層巢狀,環環相扣,很明顯,邏輯稍微複雜一些,這樣的程式就會變得難以維護。就比如我們昨天的那個回撥地獄的例子:
function funA(callback) {
console.log("A");
setTimeout(() = > {
callback()
}, 100)
}
function funB() {
console. log("B")
}
function funC(callback) {
console.log("C")
setTimeout(() = > {
callback()
}, 1000)
}
function funD() {
console.log("D")
}
function funE() {
console.log("E")
}
function funF() {
console.log("F")
}
funA(() = > {
funB()
funC(() = > {
funD()
})
funE()
})
funF()
對於這種情況,程式設計師們想了很多解決方案(比如將程式碼模組化),但流程控制上,還是沒有避免大量的巢狀。但在ES6之後的標準裡,Promise的標準化,一定程度上解決了JavaScript的流程操作問題。
什麼是Promise
在《非同步與效能》的第三章中有這麼個場景來比喻 Promise:
上面很形象的介紹了promise,上面的等待漢堡和得到漢堡,漢堡賣光了,得不到漢堡,分別對應promise的三種狀態 **pending: 進行中,既不是成功,也不是失敗狀態。 fulfilled: 意味著操作成功完成。 rejected: 意味著操作失敗。**我走到快餐店的櫃檯前,點了一個起士漢堡。並交了1.47美元的現金。通過點餐和付款,我為得到一個 值(起士漢堡)製造了一個請求。我發起了一個事務。
但是通常來說,起士漢堡不會立即到我手中。收銀員交給一些東西代替我的起士漢堡:一個帶有點餐排隊號的收據。這個點餐號是一個“我欠你”的許諾(Promise),它保證我最終會得到我的起士漢堡。
於是我就拿著我的收據和點餐號。我知道它代表我的 未來的起士漢堡,所以我無需再擔心它——除了捱餓!
在我等待的時候,我可以做其他的事情,比如給我的朋友發微信說,“嘿,一塊兒吃午餐嗎?我要吃起士漢堡”。
我已經在用我的 未來的起士漢堡 進行推理了,即便它還沒有到我手中。我的大腦可以這麼做是因為它將點餐號作為起士漢堡的佔位符號。這個佔位符號實質上使這個值 與時間無關。它是一個 未來的值。
最終,我聽到,“113號!”。於是我愉快地拿著收據走回櫃檯前。我把收據遞給收銀員,拿回我的起士漢堡。 換句話說,一旦我的 未來的值 準備好,我就用我的許諾值換回值本身。
但還有另外一種可能的輸出。它們叫我的號,但當我去取起士漢堡時,收銀員遺憾地告訴我,“對不起,看起來我們的起士漢堡賣光了。”把這種場景下顧客有多沮喪放在一邊,我們可以看到 未來的值 的一個重要性質:它們既可以表示成功也可以表示失敗。
每次我點起士漢堡時,我都知道我要麼最終得到一個起士漢堡,要麼得到起士漢堡賣光的壞訊息,並且不得不考慮中午吃點兒別的東西。
我由等待漢堡變成了等到或者等不到,這個過程不可逆,
Promise的基本用法
語法
new Promise( function(resolve, reject) {...} ); //reject引數 可不選
引數
executor
executor是帶有 resolve 和 reject 兩個引數的函式 。Promise建構函式執行時立即呼叫executor 函式, resolve 和 reject 兩個函式作為引數傳遞給executor(executor 函式在Promise建構函式返回新建物件前被呼叫)。resolve 和 reject 函式被呼叫時,分別將promise的狀態改為fulfilled(完成)或rejected(失敗)。executor 內部通常會執行一些非同步操作,一旦完成,可以呼叫resolve函式來將promise狀態改成fulfilled,或者在發生錯誤時將它的狀態改為rejected。
如果在executor函式中丟擲一個錯誤,那麼該promise 狀態為rejected。executor函式的返回值被忽略。
對更多對Promise的描述感興趣的可以 點選檢視MDN Promise下面我們開始上程式碼
新建一個Promise的例項:
let promise = new Promise((resolve, reject) = > {
setTimeout(() = > {
let random = Math.random()
if (random > 0.5) {
resolve(`resolve$ {random}`)
} else {
resolve(`reject$ {random}`)
}
}, 1000)
})
由上所示,Promise的建構函式接收一個函式作為引數,該函式接受兩個額外的函式,resolve和reject,這兩個函式分別代表將當前Promise置為fulfilled(已成功)和rejected(已失敗)兩個狀態。Promise正是通過這兩個狀態來控制非同步操作的結果。接下來我們將討論Promise的用法,實際上Promise上的例項promise是一個物件,不是一個函式。在宣告的時候,Promise傳遞的引數函式會立即執行,因此Promise使用的正確姿勢是在其外層再包裹一層函式。
let run = function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let random = Math.random()
if (random > 0.5) {
resolve(`resolve:${random}`)
} else {
reject(`reject:${random}`)
}
}, 1000)
})
}
run()
這是Promise的正常用法,接下來,就是對非同步操作結果的處理,接著上面建立的函式run()
run().then(
function(value) {
console.log(value)
})
每個Promise的例項物件,都有一個then的方法,這個方法就是用來處理之前各種非同步邏輯的結果。
then方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件的狀態變為rejected時呼叫。其中,第二個函式是可選的,不一定要提供。這兩個函式都接受Promise物件傳出的值作為引數。
下面是一個用Promise物件實現的 Ajax 操作的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript非同步</title>
</head>
<body>
<script src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
<script>
new Promise((resolve, reject) => {
$.ajax({
url: "https://easy-mock.com/mock/5c249dbe46e8386d0b21b475/example_copy_copy/promisetest",
success: res => {
if (res.code == 0) {
resolve(res.data)
} else {
reject(res.desc)
}
}
});
})
.then(res => {
console.log(res);
},err =>{
console.log(err)
})
</script>
</body>
</html>
當res.code == 0 為介面呼叫成功 輸出:
手動把 上面程式碼 res.code == 0 改為 res.code == 1 就會抱一個錯誤,輸出:
如果非同步操作獲得了我們想要的結果,那我們將呼叫resolve函式,在then的第一個作為引數的匿名函式中可以獲取資料,如果我們得到了錯誤的結果,呼叫reject函式,在then函式的第二個作為引數的匿名函式中獲取錯誤處理資料。
這樣,一個次完整的Promise呼叫就結束了。對於Promise的then()方法,then總是會返回一個Promise例項,因此你可以一直呼叫then,形如run().then().then().then().then().then()…
在一個then()方法呼叫非同步處理成功的狀態時,你既可以return一個確定的“值”,也可以再次返回一個Promise例項,當返回的是一個確切的值的時候,then會將這個確切的值傳入一個預設的Promise例項,並且這個Promise例項會立即置為fulfilled狀態,以供接下來的then方法裡使用。看程式碼:
let num = 0
let run = function() {
return new Promise(resolve => {
resolve(`${num}`)})
}
run().then(val => {
console.log(val)
return val
})
.then(val =>{
val++
console.log(val)
return val
})
.then(val =>{
val++
console.log(val)
})
輸出:
根據這個特性,我們就可以將相互依賴的多個非同步邏輯,進行比較順序的管理了,解決了讓人頭痛的回撥地獄問題。
今天我們就先到這裡,明天我們講一下Promise.then()與Promise.catch()