徹底搞懂?javascript的Promise
一、為什么要引入Promise?
在介紹本章之前,首先先拋出幾個(gè)問題:
- Promise解決了什么問題?
- Promise有哪些具體的使用場(chǎng)景?
Promise解決了什么問題?
1.回調(diào)地獄問題
在沒有Promise之前,前端獲取數(shù)據(jù)往往需要通過回調(diào)函數(shù)層層嵌套的方式來(lái)解決異步問題,例如下面這段代碼實(shí)例:
// 回調(diào)地獄實(shí)例
// 奶茶函數(shù)
function getTea(fn) {
setTimeout(() => {
fn('獲取到一杯奶茶')
},2000)
}
// 面包函數(shù)
function getBread(fn) {
setTimeout(() => {
fn('獲取到一個(gè)面包')
},100)
}
// 如果必須按照順序獲取,而不是根據(jù)時(shí)間,要求是先獲取到奶茶后獲取到面包。
getTea(function(data) {
console.log(data);
getBread(function(data) {
console.log(data);
})
})
2.可讀性問題
通過Promise我們可以將上面的代碼重寫為下面的方式,明顯這樣可讀性更高。
// 下面解釋下,如何通過Promise來(lái)解決回調(diào)地獄的問題
function getTea() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('獲取到一杯奶茶')
}, 2000)
})
}
function getBread() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('獲取到一個(gè)面包')
}, 500)
})
}
getTea()
.then(res => {
console.log(res);
return getBread();
})
.then(res => {
console.log(res);
})
3.信任問題(也叫回調(diào)多次執(zhí)行問題)
傳統(tǒng)的回調(diào)函數(shù)無(wú)法保證只被執(zhí)行一次,回調(diào)函數(shù)還要可能被執(zhí)行其他操作,而Promise調(diào)用且僅調(diào)用一次resolve,不會(huì)產(chǎn)生回調(diào)多次執(zhí)行的問題,所以Promise很好的解決了第三方庫(kù)多次調(diào)用回調(diào)的問題。
Promise有哪些具體的使用場(chǎng)景?
- 場(chǎng)景1:將圖片的加載寫成一個(gè)Promise,圖片一旦加載完成,Promise的狀態(tài)就會(huì)發(fā)生變化。
- 場(chǎng)景2:當(dāng)下一個(gè)異步請(qǐng)求需要依賴上一個(gè)請(qǐng)求結(jié)果的時(shí)候,可以通過鏈?zhǔn)讲僮鹘鉀Q問題。
- 場(chǎng)景3:通過all()實(shí)現(xiàn)多個(gè)請(qǐng)求合并在一起,匯總所有的請(qǐng)求結(jié)果,只需設(shè)置一個(gè)loading即可。
- 場(chǎng)景4:通過race()可以設(shè)置圖片請(qǐng)求超時(shí)。
二、手寫Prromise身上的方法
手寫Promise.all
Promise.all的特點(diǎn)是接收的是一個(gè)可迭代對(duì)象,當(dāng)這個(gè)可迭代對(duì)象中的所有元素都執(zhí)行成功會(huì)返回一個(gè)數(shù)組,一個(gè)出錯(cuò)則立即返回錯(cuò)誤。
function myPromiseAll(iterable) {
// 首先明確要返回的對(duì)象是一個(gè)Promise
return new Promise((resolve,reject) => {
// 首先將可迭代對(duì)象轉(zhuǎn)換為數(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ù)接收的是一個(gè)可迭代對(duì)象,相當(dāng)于讓這個(gè)可迭代對(duì)象中的所有promise對(duì)象進(jìn)行賽跑,只要有一個(gè)promise對(duì)象發(fā)生了狀態(tài)變化,那么直接返回這個(gè)promise對(duì)象返回的結(jié)果。
// 手寫promise.race
function myPromiseRace(iterator) {
// 首先返回的是一個(gè)promise對(duì)象
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的特點(diǎn)
- 無(wú)論成功還是失敗,都會(huì)執(zhí)行這個(gè)方法
- 返回的是一個(gè)Promise
Promise.finally執(zhí)行的例子
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(111);
},2000)
})
p.then(res => {
console.log(res); // 111
}).finally(() => {
console.log('無(wú)論如何這里都會(huì)被執(zhí)行'); // 無(wú)論如何這里都會(huì)被執(zhí)行
})
手寫Promise.finally(Promise.finally返回的本質(zhì)上是一個(gè)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í)候返回的是一個(gè)結(jié)果數(shù)組,而失敗的時(shí)候返回的是最先被reject的值。當(dāng)Promise.all()的結(jié)果是成功的時(shí)候,返回結(jié)果的數(shù)組里邊的數(shù)據(jù)順序和Promise.all()接收到的promise順序是一致的。
promise.race表示多個(gè)Promise賽跑的意思,里面哪個(gè)結(jié)果執(zhí)行的快就返回哪個(gè)結(jié)果,不管結(jié)果本身是成功還是失敗,其他Promise代碼還會(huì)執(zhí)行,只是不會(huì)返回。
Promise.all和Promise.race的應(yīng)用場(chǎng)景
promise.all()的應(yīng)用場(chǎng)景
多個(gè)異步任務(wù)都得到結(jié)果時(shí),進(jìn)行顯示的場(chǎng)景
比如,當(dāng)用戶點(diǎn)擊按鈕時(shí),會(huì)彈出一個(gè)對(duì)話框,這個(gè)對(duì)話框中的數(shù)據(jù)來(lái)自兩個(gè)不同的后端接口獲取的數(shù)據(jù),當(dāng)用戶剛點(diǎn)擊的時(shí)候,顯示的時(shí)數(shù)據(jù)加載中的狀態(tài),當(dāng)這兩部分?jǐn)?shù)據(jù)都從接口獲取到數(shù)據(jù)的時(shí)候,才讓數(shù)據(jù)加載中的狀態(tài)消失,此時(shí)就可以使用Promise.all方法。
Promise.race()的應(yīng)用場(chǎng)景
提示用戶請(qǐng)求超時(shí)
比如,當(dāng)用戶點(diǎn)擊按鈕發(fā)送請(qǐng)求的時(shí)候,當(dāng)后端的接口超過我們?cè)O(shè)定的時(shí)間還沒有獲取到數(shù)據(jù)的時(shí)候,我們就可以提示用戶請(qǐng)求超時(shí)。
三、Promise是如何解決串行和并行的?
什么是并行?什么是串行?
并行:指的是多個(gè)異步請(qǐng)求同時(shí)進(jìn)行。
串行:一個(gè)異步請(qǐng)求完成之后再進(jìn)行下一個(gè)請(qǐng)求。
Promise實(shí)現(xiàn)并行請(qǐng)求
Promise實(shí)現(xiàn)并行請(qǐng)求主要是依靠Promise.all方法和Promise.race方法,我們可以通過手寫Promise.all方法或Promise.race方法來(lái)實(shí)現(xiàn)這一目標(biāo)。
Promise實(shí)現(xiàn)串行請(qǐng)求
Promise實(shí)現(xiàn)串行請(qǐng)求主要是借助reduce函數(shù)??梢詤⒖嘉业倪@篇文章如何控制Promise的串行執(zhí)行?
// 借助reduce函數(shù)來(lái)實(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ù),則可能會(huì)發(fā)生值穿透。Promise方法通過return傳值,沒有return就只是相互獨(dú)立的任務(wù)而已。看看下面這個(gè)例子的輸出可能會(huì)更好的幫助我們理解什么是值穿透?
Promise.resolve(1)
.then(function(){return 2})
.then(Promise.resolve(3))
.then(console.log) // 2
之所以發(fā)生了值穿透就是因?yàn)榈诙€(gè)then中傳入的不是一個(gè)函數(shù)的形式。
五、使用Promise封裝Ajax請(qǐng)求
使用Promise封裝Ajax請(qǐng)求的關(guān)鍵步驟,全部在下面的代碼中的注釋里,詳情請(qǐng)看下面的代碼。
// 使用Promise封裝Ajax請(qǐng)求
const res = new Promise((resolve,reject) => {
// 1. 創(chuàng)建一個(gè)XMLHttpRequest對(duì)象
const xhr = new XMLHttpRequest();
// 2. 初始化請(qǐng)求方法和URL
xhr.open('GET','https://api.apiopen.top/getJoke');
// 3. 發(fā)送請(qǐng)求
xhr.send();
// 4. 綁定事件,處理響應(yīng)結(jié)果
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
// 這里4代表的就是說服務(wù)端返回了全部的結(jié)果
// 如果服務(wù)端返回的狀態(tài)碼是2開頭的,我們就resolve這個(gè)返回的結(jié)果,反之則reject對(duì)應(yīng)的狀態(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來(lái)獲取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()
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
JavaScript數(shù)據(jù)結(jié)構(gòu)和算法之圖和圖算法
這篇文章主要介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)和算法之圖和圖算法,本文講解了有向圖、無(wú)序圖、簡(jiǎn)單圖、圖的遍歷等內(nèi)容,需要的朋友可以參考下2015-02-02
javascript 繼承學(xué)習(xí)心得總結(jié)
下面小編就為大家?guī)?lái)一篇javascript 繼承學(xué)習(xí)心得總結(jié)。小編覺得挺不錯(cuò)的?,F(xiàn)在分享給大家。給大家做個(gè)參考2016-03-03
在JavaScript中處理時(shí)間之setMinutes()方法的使用
這篇文章主要介紹了在JavaScript中處理時(shí)間之setMinutes()方法的使用,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06
微信公眾號(hào)開發(fā) 自定義菜單跳轉(zhuǎn)頁(yè)面并獲取用戶信息實(shí)例詳解
這篇文章主要介紹了微信公眾號(hào)開發(fā) 自定義菜單跳轉(zhuǎn)頁(yè)面并獲取用戶信息實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-12-12

