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