盤點JavaScript中的Promise鏈的高級用法
一、前言
有一系列的異步任務要一個接一個地執(zhí)行 — 例如,加載腳本。如何寫出更好的代碼呢?
Promise 提供了一些方案來做到這一點。
二、案例分析
1.運行流程如下
它的理念是將 result 通過 .then 處理程序(handler)鏈進行傳遞。
//1. 初始 promise 在 1 秒后進行 resolve (*),
//2. 然后 .then 處理程序(handler)被調(diào)用 (**)。
//3. 它返回的值被傳入下一個 .then 處理程序(handler)(***)。
之所以這么運行,是因為對 promise.then 的調(diào)用會返回了一個 promise,所以可以在其之上調(diào)用下一個 .then。
當處理程序(handler)返回一個值時,它將成為該 promise 的 result,所以將使用它調(diào)用下一個 .then。
新手常犯的一個經(jīng)典錯誤:從技術(shù)上講,也可以將多個 .then 添加到一個 promise 上。但這并不是 promise 鏈(chaining)。
例 :
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
在這里所做的只是一個 promise 的幾個處理程序(handler)。他們不會相互傳遞 result;相反,它們之間彼此獨立運行處理任務。
例1:fetch
在前端編程中,promise 通常被用于網(wǎng)絡請求。
案例:
將使用 [etch方法從遠程服務器加載用戶信息。它有很多可選的參數(shù)。
let promise = fetch(url);
執(zhí)行這條語句,向 url 發(fā)出網(wǎng)絡請求并返回一個 promise。當遠程服務器返回 header(是在 全部響應加載完成前)時,該 promise 用使用一個 response 對象來進行 resolve。
為了讀取完整的響應,應該調(diào)用 response.text() 方法:當全部文字(full text)內(nèi)容從遠程服務器下載完成后,它會返回一個 promise,該 promise 以剛剛下載完成的這個文本作為 result 進行 resolve。
下面這段代碼向 user.json 發(fā)送請求,并從服務器加載該文本:
fetch('/article/promise-chaining/user.json')
// 當遠程服務器響應時,下面的 .then 開始執(zhí)行
.then(function(response) {
// 當 user.json 加載完成時,response.text() 會返回一個新的 promise
// 該 promise 以加載的 user.json 為 result 進行 resolve
return response.text();
})
.then(function(text) {
// ...這是遠程文件的內(nèi)容
alert(text); // {"name": "iliakan", "isAdmin": true}
});
從 fetch 返回的 response 對象還包括 response.json() 方法,該方法讀取遠程數(shù)據(jù)并將其解析為 JSON。在的例子中,這更加方便,所以讓切換到這個方法。
為了簡潔,還將使用箭頭函數(shù):
// 同上,但是使用 response.json() 將遠程內(nèi)容解析為 JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan, got user name
現(xiàn)在,讓用加載好的用戶信息搞點事情。
例如,可以多發(fā)一個到 GitHub 的請求,加載用戶個人資料并顯示頭像:
// 發(fā)送一個對 user.json 的請求
fetch('/article/promise-chaining/user.json')
// 將其加載為 JSON
.then(response => response.json())
// 發(fā)送一個到 GitHub 的請求
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// 將響應加載為 JSON
.then(response => response.json())
// 顯示頭像圖片(githubUser.avatar_url)3 秒(也可以加上動畫效果)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
這段代碼可以工作,具體細節(jié)請看注釋。但是,這兒有一個潛在的問題,一個新手使用 promise 的典型問題。
請看 (*) 行:如何能在頭像顯示結(jié)束并被移除 之后 做點什么?例如,想顯示一個用于編輯該用戶或者其他內(nèi)容的表單。就目前而言,是做不到的。
為了使鏈可擴展,需要返回一個在頭像顯示結(jié)束時進行 resolve 的 promise。
就像這樣:
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) { // (*)
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser); // (**)
}, 3000);
}))
// 3 秒后觸發(fā)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
注:
也就是說,第 (*) 行的 .then 處理程序(handler)現(xiàn)在返回一個 new Promise,只有在 setTimeout 中的 resolve(githubUser) (**) 被調(diào)用后才會變?yōu)?settled。鏈中的下一個 .then 將一直等待這一時刻的到來。
作為一個好的做法,異步行為應該始終返回一個 promise。這樣就可以使得之后計劃后續(xù)的行為成為可能。即使現(xiàn)在不打算對鏈進行擴展,但之后可能會需要。
三、總結(jié)
本文基于JavaScript基礎,介紹了Promise 鏈的高級用法,主要介紹了使用Promise時新手常會出現(xiàn)的幾個問題,對這幾個問題進行詳細的解答。
通過案例的分析,能夠更直觀的展示。采用JavaScript語言,能夠幫助你更好的學習JavaScript。
代碼很簡單。希望能夠幫助你更好的學習。