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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章