1. 程式人生 > >JavaScript非同步與Promise

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:

  我走到快餐店的櫃檯前,點了一個起士漢堡。並交了1.47美元的現金。通過點餐和付款,我為得到一個 值(起士漢堡)製造了一個請求。我發起了一個事務。

  但是通常來說,起士漢堡不會立即到我手中。收銀員交給一些東西代替我的起士漢堡:一個帶有點餐排隊號的收據。這個點餐號是一個“我欠你”的許諾(Promise),它保證我最終會得到我的起士漢堡。

  於是我就拿著我的收據和點餐號。我知道它代表我的 未來的起士漢堡,所以我無需再擔心它——除了捱餓!

  在我等待的時候,我可以做其他的事情,比如給我的朋友發微信說,“嘿,一塊兒吃午餐嗎?我要吃起士漢堡”。

  我已經在用我的 未來的起士漢堡 進行推理了,即便它還沒有到我手中。我的大腦可以這麼做是因為它將點餐號作為起士漢堡的佔位符號。這個佔位符號實質上使這個值 與時間無關。它是一個 未來的值。

  最終,我聽到,“113號!”。於是我愉快地拿著收據走回櫃檯前。我把收據遞給收銀員,拿回我的起士漢堡。 換句話說,一旦我的 未來的值 準備好,我就用我的許諾值換回值本身。

  但還有另外一種可能的輸出。它們叫我的號,但當我去取起士漢堡時,收銀員遺憾地告訴我,“對不起,看起來我們的起士漢堡賣光了。”把這種場景下顧客有多沮喪放在一邊,我們可以看到 未來的值 的一個重要性質:它們既可以表示成功也可以表示失敗。

  每次我點起士漢堡時,我都知道我要麼最終得到一個起士漢堡,要麼得到起士漢堡賣光的壞訊息,並且不得不考慮中午吃點兒別的東西。

  我由等待漢堡變成了等到或者等不到,這個過程不可逆,

上面很形象的介紹了promise,上面的等待漢堡和得到漢堡,漢堡賣光了,得不到漢堡,分別對應promise的三種狀態 **pending: 進行中,既不是成功,也不是失敗狀態。 fulfilled: 意味著操作成功完成。 rejected: 意味著操作失敗。**

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()