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()
友情鏈接:點擊查看所有文章目錄