徹底搞懂?javascript的Promise
一、為什么要引入Promise?
在介紹本章之前,首先先拋出幾個問題:
- Promise解決了什么問題?
- Promise有哪些具體的使用場景?
Promise解決了什么問題?
1.回調地獄問題
在沒有Promise之前,前端獲取數(shù)據(jù)往往需要通過回調函數(shù)層層嵌套的方式來解決異步問題,例如下面這段代碼實例:
// 回調地獄實例
// 奶茶函數(shù)
function getTea(fn) {
setTimeout(() => {
fn('獲取到一杯奶茶')
},2000)
}
// 面包函數(shù)
function getBread(fn) {
setTimeout(() => {
fn('獲取到一個面包')
},100)
}
// 如果必須按照順序獲取,而不是根據(jù)時間,要求是先獲取到奶茶后獲取到面包。
getTea(function(data) {
console.log(data);
getBread(function(data) {
console.log(data);
})
})
2.可讀性問題
通過Promise我們可以將上面的代碼重寫為下面的方式,明顯這樣可讀性更高。
// 下面解釋下,如何通過Promise來解決回調地獄的問題
function getTea() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('獲取到一杯奶茶')
}, 2000)
})
}
function getBread() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('獲取到一個面包')
}, 500)
})
}
getTea()
.then(res => {
console.log(res);
return getBread();
})
.then(res => {
console.log(res);
})
3.信任問題(也叫回調多次執(zhí)行問題)
傳統(tǒng)的回調函數(shù)無法保證只被執(zhí)行一次,回調函數(shù)還要可能被執(zhí)行其他操作,而Promise調用且僅調用一次resolve,不會產(chǎn)生回調多次執(zhí)行的問題,所以Promise很好的解決了第三方庫多次調用回調的問題。
Promise有哪些具體的使用場景?
- 場景1:將圖片的加載寫成一個Promise,圖片一旦加載完成,Promise的狀態(tài)就會發(fā)生變化。
- 場景2:當下一個異步請求需要依賴上一個請求結果的時候,可以通過鏈式操作解決問題。
- 場景3:通過all()實現(xiàn)多個請求合并在一起,匯總所有的請求結果,只需設置一個loading即可。
- 場景4:通過race()可以設置圖片請求超時。
二、手寫Prromise身上的方法
手寫Promise.all
Promise.all的特點是接收的是一個可迭代對象,當這個可迭代對象中的所有元素都執(zhí)行成功會返回一個數(shù)組,一個出錯則立即返回錯誤。
function myPromiseAll(iterable) {
// 首先明確要返回的對象是一個Promise
return new Promise((resolve,reject) => {
// 首先將可迭代對象轉換為數(shù)組
const promises = Array.from(iterable);
let flag = 0;
const result = [];
// 開始遍歷執(zhí)行
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(res => {
result[i] = res;
flag++;
if (flag === promises.length) {
resolve(result)
}
}).catch(err => {
reject(err)
})
}
})
}
手寫Promise.race
Promise.race函數(shù)接收的是一個可迭代對象,相當于讓這個可迭代對象中的所有promise對象進行賽跑,只要有一個promise對象發(fā)生了狀態(tài)變化,那么直接返回這個promise對象返回的結果。
// 手寫promise.race
function myPromiseRace(iterator) {
// 首先返回的是一個promise對象
return new Promise((resolve,reject) => {
for (let item of iterator) {
Promise.resolve(item).then(res => {
resolve(item);
}).catch(err => {
reject(err);
})
}
})
}
let p1 = new Promise(resolve => {
setTimeout(resolve, 105, 'p1 done')
})
let p2 = new Promise(resolve => {
setTimeout(resolve, 100, 'p2 done')
})
myPromiseRace([p1, p2]).then(data => {
console.log(data); // p2 done
})
手寫Promise.finally
Promise.finally的特點
- 無論成功還是失敗,都會執(zhí)行這個方法
- 返回的是一個Promise
Promise.finally執(zhí)行的例子
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(111);
},2000)
})
p.then(res => {
console.log(res); // 111
}).finally(() => {
console.log('無論如何這里都會被執(zhí)行'); // 無論如何這里都會被執(zhí)行
})
手寫Promise.finally(Promise.finally返回的本質上是一個then方法,需要在then方法中執(zhí)行我們傳入的參數(shù),然后返回形參)
Promise.prototype.finally = function(f) {
return this.then((value) => {
return Promise.resolve(f()).then(() => value)
},(err) => {
return Promise.resolve(f()).then(() => {
throw err;
})
})
}
Promise.all和Promise.race的區(qū)別
Promise.all()成功和失敗的返回值是不同的,成功的時候返回的是一個結果數(shù)組,而失敗的時候返回的是最先被reject的值。當Promise.all()的結果是成功的時候,返回結果的數(shù)組里邊的數(shù)據(jù)順序和Promise.all()接收到的promise順序是一致的。
promise.race表示多個Promise賽跑的意思,里面哪個結果執(zhí)行的快就返回哪個結果,不管結果本身是成功還是失敗,其他Promise代碼還會執(zhí)行,只是不會返回。
Promise.all和Promise.race的應用場景
promise.all()的應用場景
多個異步任務都得到結果時,進行顯示的場景
比如,當用戶點擊按鈕時,會彈出一個對話框,這個對話框中的數(shù)據(jù)來自兩個不同的后端接口獲取的數(shù)據(jù),當用戶剛點擊的時候,顯示的時數(shù)據(jù)加載中的狀態(tài),當這兩部分數(shù)據(jù)都從接口獲取到數(shù)據(jù)的時候,才讓數(shù)據(jù)加載中的狀態(tài)消失,此時就可以使用Promise.all方法。
Promise.race()的應用場景
提示用戶請求超時
比如,當用戶點擊按鈕發(fā)送請求的時候,當后端的接口超過我們設定的時間還沒有獲取到數(shù)據(jù)的時候,我們就可以提示用戶請求超時。
三、Promise是如何解決串行和并行的?
什么是并行?什么是串行?
并行:指的是多個異步請求同時進行。
串行:一個異步請求完成之后再進行下一個請求。
Promise實現(xiàn)并行請求
Promise實現(xiàn)并行請求主要是依靠Promise.all方法和Promise.race方法,我們可以通過手寫Promise.all方法或Promise.race方法來實現(xiàn)這一目標。
Promise實現(xiàn)串行請求
Promise實現(xiàn)串行請求主要是借助reduce函數(shù)??梢詤⒖嘉业倪@篇文章如何控制Promise的串行執(zhí)行?
// 借助reduce函數(shù)來實現(xiàn)Promise的串行執(zhí)行
const funcArr = [
() => {
return new Promise((resolve) => {
setTimeout(() => {resolve(1)},2000)
})
},
() => {
return new Promise((resolve) => {
setTimeout(() => {resolve(2)},1000)
})
},
() => {
return new Promise((resolve) => {
setTimeout(() => {resolve(3)},3000)
})
},
];
function inOrder(arr) {
const res = [];
return new Promise((resolve) => {
arr.reduce((pre,cur) => {
return pre.then(cur).then(data => res.push(data))
},Promise.resolve()).then(data => resolve(res))
})
}
inOrder(funcArr).then(data => console.log(data)) // [1,2,3]
四、什么是Promise穿透?
所謂的Promise的值穿透指的是.then或者.catch的參數(shù)希望是函數(shù),如果傳入的不是函數(shù),則可能會發(fā)生值穿透。Promise方法通過return傳值,沒有return就只是相互獨立的任務而已。看看下面這個例子的輸出可能會更好的幫助我們理解什么是值穿透?
Promise.resolve(1)
.then(function(){return 2})
.then(Promise.resolve(3))
.then(console.log) // 2
之所以發(fā)生了值穿透就是因為第二個then中傳入的不是一個函數(shù)的形式。
五、使用Promise封裝Ajax請求
使用Promise封裝Ajax請求的關鍵步驟,全部在下面的代碼中的注釋里,詳情請看下面的代碼。
// 使用Promise封裝Ajax請求
const res = new Promise((resolve,reject) => {
// 1. 創(chuàng)建一個XMLHttpRequest對象
const xhr = new XMLHttpRequest();
// 2. 初始化請求方法和URL
xhr.open('GET','https://api.apiopen.top/getJoke');
// 3. 發(fā)送請求
xhr.send();
// 4. 綁定事件,處理響應結果
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
// 這里4代表的就是說服務端返回了全部的結果
// 如果服務端返回的狀態(tài)碼是2開頭的,我們就resolve這個返回的結果,反之則reject對應的狀態(tài)碼
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject(xhr.status)
}
}
}
})
res.then(function(value) {
console.log(value);
},function(err) {
console.log(err);
})
六、Promise有哪些狀態(tài)?
Promise主要有以下三種狀態(tài):
- pending狀態(tài)(初始狀態(tài))
- fulfilled狀態(tài)(已經(jīng)成功的狀態(tài))
- rejected狀態(tài)(已經(jīng)失敗的狀態(tài))
Promise狀態(tài)的變化過程
1.從pending到fulfilled狀態(tài)的切換
resolve前是pending狀態(tài),resolve之后是fulfilled狀態(tài)
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve前的狀態(tài):', p);
resolve();
console.log('resolve之后的狀態(tài)', p);
})
})

2.從pending狀態(tài)到rejected狀態(tài)
reject前是pending狀態(tài),reject之后是rejected狀態(tài)。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('reject前的狀態(tài):', p);
reject();
console.log('reject之后的狀態(tài)', p);
})
})

七、將callback改寫成Promise
1.傳統(tǒng)callback的形式
const fs = require('fs');
fs.readFile('./temp.md',(err,data) => {
console.log(data.toString());
})
2.將callback改為promise的形式
核心就是通過resolve來獲取callback的數(shù)據(jù)。
const fs = require('fs');
async function myReadFile() {
let result = await new Promise((resolve,reject) => {
fs.readFile('./temp.md',(err,data) => {
resolve(data.toString());
})
})
console.log(result); // xxxxx
return result;
}
myReadFile()
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
相關文章
JavaScript arguments 多參傳值函數(shù)
在一個函數(shù)體內,標識符arguments引用了arguments對象的一個特殊屬性。可以按照數(shù)目(而不是名字)獲取傳遞給函數(shù)的參數(shù)值。2010-10-10
詳解JavaScript中setSeconds()方法的使用
這篇文章主要介紹了詳解JavaScript中setSeconds()方法的使用,是JS入門學習中的基礎知識,需要的朋友可以參考下2015-06-06
js中escape對應的C#解碼函數(shù) UrlDecode
js中escape對應的C#解碼函數(shù) System.Web.HttpUtility.UrlDecode(s),使用過程中有以下幾點需要注意2012-12-12
Array.slice()與Array.splice()的返回值類型
Array.slice()與Array.splice()的返回值類型...2006-10-10
Javascript學習筆記之函數(shù)篇(六) : 作用域與命名空間
本文主要講述了javascript中作用域和命名空間的區(qū)別,十分的詳細,這里推薦給大家,希望小伙伴能有所收獲2014-11-11

