ES6之-優雅的Promise
文章目錄
- ES6之-優雅的Promise
- 前言
- 異步編程
- 以往的異步編程模式
- 正篇:
- Promise的三種狀態
- resolve.then、reject.catch
- 原始碼分析:
- Promise
- then函式的另外一種使用格式
- Promise的鏈式編程思想
- Promise.all
前言
提示:再講正篇之前我們可以簡單的了解一下JavaScript的異步編程的歷程,這將使你對Promise的理解更深一步,如果足夠了解請前往目錄正篇
🐖:想要學習Vue,強烈推薦去BiliBili大學學習coderwhy老師的視頻課
Vue-coderwhy
異步編程
Promise又叫期約,我們在講解Promise之前,希望大家先了解一個概念:異步編程
特別是在JavaScript這種單執行緒事件回圈模型中,同步操作與異步操作更是代碼所要依賴的核心機制,異步行為是為了優化因計算機量大而時間長的操作,如果在等待其他操作完成的同時,即使運行其他指令,系統也能保持穩定,那么這樣就是務實的
顧名思義:
- 同步:記憶體中順序執行的處理指令
- 異步:類似于系統中斷,異步操作經常是必要的,因為強制行程等待一個長時間的操作(同步操作則必須要等)是絕對不可取的
以往的異步編程模式
在早期的JavaScript中,只支持定義回呼函式來表明異步操作完成,串聯多個異步操作是一個常見的問題,通常需要深度嵌套的回呼函式(**回呼地獄**)
setTimeout可以定義一個在指定時間之后會被調度執行的回呼函式,這是它被稱作異步函式的原因
function invoke(){
setTimeout( () => {
console.log('函式將在兩秒后執行');
},2000)
}
invoke();
所以同樣的我們可以用setTimeout來進行很多操作:
- 設定一個異步回傳值
- 失敗處理
- 嵌套異步回呼
但是寫出層層嵌套的回呼函式,如果嵌套過多,會極大影響代碼可讀性和邏輯,這種情況也被稱作回呼地獄,
我們當然希望以一種更優雅的方式去處理異步編程
那就是 Promise
總結:
簡單來說Promise就是對異步編程進行優雅封裝
正篇:
綜上:
| Promise就是異步編程的解決方案 |
我們在處理復雜的網路請求或者其他的一些異步操作的時候,同步會導致行程的阻塞,等待行程導致頁面無法顯示這就太可笑了
并且早期的異步編程代碼冗雜可觀性極差,這就用到了Promise
| 優雅的Promise |
Promise本身是一個物件,是一個建構式,它需要傳入一個引數,并且這個引數必須是函式
new Promise(引數)
new Promise(() => {
//用箭頭函式的寫法傳入一個函式
})
傳入的這個函式本身包含兩個引數resolve和reject
new Promise((resolve,reject) => {
})
注意:resolve和reject本身又是一個函式
因為傳入的是函式,所以我們可以直接簡單的寫入一個定時器函式
new Promise((resolve,reject) => {
setTimeout( ()=> {
})
})
Promise的三種狀態
- 待定(pending)
- 兌現(fulfilled,有時也稱為解決,resolve)
- 拒絕(rejected)
待定(pending)就是期約最初始的狀態
- 成功就將待定轉換為解決狀態
- 失敗就將待定轉換為拒絕狀態
- 注意一旦轉換為兩種狀態,就再也無法改變,無論落定為哪種狀態都是
不可逆的
有兩種過度:
pending -> fulfilled或者是pending -> rejected
錯誤機制:
如果錯誤已經捕獲了,那么錯誤不會繼續傳遞下去 如果錯誤沒有被捕獲,那么錯誤會隱式傳遞下去,直到有錯誤處理函式來捕獲這個錯誤
resolve.then、reject.catch
- then方法接受一個引數-resolve回傳的資料(正常時)
- catch方法接收一個引數-reject回傳的資訊(拋出例外)
我們只需要記住
- resolved(解決)時會執行then(繼續)
- rejected(拒絕)時會執行catch(捕獲例外)
簡單的實體:
new Promise((resolve,reject) => {
setTimeout(()=> {
//resolve('success')
reject('Error Data')
},1000)
}).then((data)=>{
console.log(data);
}).catch((data)=> {
console.log(data);
})

原始碼分析:
Promise
我們ctrl點擊進入Promise


學過Java的不難看出Promise其實是一個類,用來實作 PromiseConstructor這個介面,new Promise其實是new一個物件實體呼叫建構式
interface PromiseConstructor
executor是exe的全名:譯為可執行,里面定義了resolve和reject兩個函式
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
then函式的另外一種使用格式
我們來看原始碼定義Promise的介面

注意這個then函式,我們把它單獨拿出來分析
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
這一段和最后一小段是泛型,我們先不管它
then<TResult1 = T, TResult2 = never>
有沒有看到后面一大段內容被一個逗號分隔開,這其實本身又是兩個函式
第一個函式會在onfulfilled(解決)時候執行
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
第二個函式會在onrejected(拒絕)時候執行
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
| 表示并集,所以這個可以傳undefined和null
所以可以寫作下面的樣式
new Promise((resolve,reject) => {
setTimeout(()=> {
//resolve('success')
reject('Error Data')
},1000)
}).then(函式1,函式2)
因為本質就是resolve執行then和
reject執行catch
所以分析原始碼我們就可以把.catch.then方法全部寫到.then里,
new Promise((resolve,reject) => {
setTimeout(()=> {
//resolve('success')
reject('Error Data')
},1000)
}).then(data=>{
console.log(data)//resolve時執行
},err=>{
console.log(err)//reject時執行
})

Promise的鏈式編程思想
為了方便大家理解,我們用簡單的資料或者輸出代替復雜的網路請求
假如有一個需求:
我們需要進行多次網路請求時,并對請求回來的資料進行大量代碼處理
| 不使用Promise |
new Promise((resolve,reject) => {
setTimeout(()=> {
console.log('這里是第一次網路請求回來的資料處理代碼');
console.log('大約有100行');
setTimeout(()=> {
console.log('這里是第二次網路請求回來的資料處理代碼');
console.log('大約有100行');
setTimeout(() => {
console.log('這里是第三次網路請求回來的資料處理代碼')
console.log('大約有100行')
},1000)
},1000)
},1000)
})
有沒有發現,真的是層層嵌套,你現在可能看似邏輯清晰但把資料操作的代碼(正常處理+錯誤捕獲)放進去你就會徹底蒙圈了,
深層嵌套+資料邏輯處理
這真的是地獄
| 我們在來看看使用Promise |
我們知道,執行resolve函式會跳轉到then()方法,我們把請求回來的資料進行的操作代碼全都放到then()方法里面,最后在代碼的結尾return
一個Promise實體繼續進行鏈式操作
reslove.then-return 實體-reslove.then重復下去
new Promise((resolve,reject) => {
setTimeout(()=> {
resolve()
},1000)
}).then(() => {
console.log('這里是第一次網路請求回來的資料處理代碼');
console.log('大約有100行');
return new Promise((resolve,reject) => {
setTimeout(()=> {
resolve()
},1000)
}).then(() => {
console.log('這里是第二次網路請求回來的資料處理代碼');
console.log('大約有100行');
return new Promise((resolve,reject) => {
setTimeout(()=> {
resolve()
},1000)
}).then(() => {
console.log('這里是第一次網路請求回來的資料處理代碼');
console.log('大約有100行');
})
這就是所謂的鏈式編程,從嵌套到鏈式
它的主要特點就是能把原來對異步編程嵌套的寫法進行抽離和分離
這種代碼可以將資料處理的代碼單獨放在then或catch里,乍一看可能覺得還沒上面好,那是因為對Promise應用的還不熟練,當你開始使用才會發現它的優雅性是名不虛傳
Promise.all
我們舉一些簡單的實體來講解
我們可能有如下需求:
我們現在有多個網路請求,并且需要所有請求結果都拿到之后進行下一步操作
我們想要判斷兩個請求都拿到了,該怎么做呢,平常思維:
并且我們并不能判斷哪一個網路請求先拿到,如果已經清楚第一個先拿第二個后拿,那我們只需要在第二個做統一處理就好了,但此時我們只能兩個都做處理
設定兩個默認為false的變數,某一個請求拿到之后賦值為true,最后做判斷兩個變數同時為true時進行下一步操作
let isResult1 = false
let isResult2 = false
//請求1
$.ajax({
url:'',
success:function(){
console.log('結果1');
isResult1 = true
handleResult()
}
})
//請求2
$.ajax({
url:'',
success:function(){
console.log('結果2');
isResult2 = true
handleResult()
}
})
function handleResult(){
if(isResult1&&isResult2 ){
//同時為true繼續執行操作
}
}
| 我們再來看看Promise.all |
我們看它的原始碼可以知道,values:后面看著像陣列,不過官方給出的叫可迭代物件(可遍歷,因此傳入可以是陣列)

Promise.all的特點就在于,你把多個網路請求作為陣列寫入,它內部會幫你自動判斷這幾個網路請求操作是否都完成,如果全都完成則會幫你執行.then
Promise.all([
new Promise((reslove,reject) => {
$.ajax({
url:'url1',
success:function(data){
resolve(data)
}
})
}),
new Promise((reslove,reject) => {
$.ajax({
url:'url2',
success:function(data){
resolve(data)
}
})
})//then函式里面傳入的是一個陣列,并且陣列里面包含著兩個請求的結果
]).then(array => {
array[0]
array[1]
//此時就可以繼續對其操作
console.log('array[0]','array[1]')
})
是不是比上面的操作簡單的多,你學廢了嗎
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/255918.html
標籤:其他
上一篇:Echarts南丁格爾玫瑰圖和Highcharts可變寬度的環形餅圖
下一篇:前端工程化的理解
