一、為什麼使用Promise?

我們知道 js 執行的時候,一次只能執行一個任務,它會阻塞其他任務。由於這個缺陷導致 js 的所有網路操作,瀏覽器事件,都必須是非同步執行。非同步執行可以使用回撥函式執行。

常見的非同步模式有以下幾種:

  • 定時器
  • 介面呼叫
  • 事件函式
// setTimeout 示例
function callBack(){
console.log('執行完成')
}
console.log('before setTimeout')
setTimeout(callBack,1000)// 1秒後呼叫callBack函式
console.log('after setTimeout')

執行後控制檯輸出結果為:

before setTimeout
after setTimeout
執行完成 //1秒後列印

上述定時器是在固定時間觸發某個回撥函式。

對於 ajax 網路請求就沒有這麼簡單了,可能有多個網路請求是關聯的,先執行某個請求返回結果後,第一個返回結果作為第二個請求的引數,呼叫第二個網路請求。如此,如果業務複雜,網路請求太多時,回撥也很多,容易出現回撥地獄。所以 Promise 出現了,專門解決非同步回撥地獄問題。

Promise 翻譯成中文:承諾、保證。

通俗地講,Promise 就像一個容器,裡面存放著未來才會結束,返回結果的容器,返回的結果只需要在出口處接收就好了。從語法上講,Promise 是一個物件,從它可以獲取非同步操作的訊息。

二、Promise基本使用

下列用到的所有定時器模擬我們的 ajax 請求。

Promise 例項化的時候,傳入的引數是一個函式,函式中接收兩個引數:

const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('123')
},1000)
}).then(res=>{
console.log(res) //1秒後列印123
})

傳入的 resolve 和 reject 本身都是函式。其作用分別為:

resolve - 把 Promise 的狀態從進行中變為成功狀態。

reject - 把 Promise 的狀態從進行中變為拒絕狀態。

Promise的三種狀態:

pending :進行中,表示 Promise 還在執行階段,沒有執行完成。

fulfilled:成功狀態,表示 Promise 成功執行完成。

rejected:拒絕狀態,表示 Promise 執行被拒絕,也就是失敗。

Promise 的狀態,只可能是其中一種狀態,從進行中變為成功或失敗狀態之後,狀態就固定了,不會再發生改變。

Promise.then

執行 resolve 時,Promise 狀態變為 fulfilled ,會執行 .then 方法。then 方法接收的引數也是一個函式,函式中攜帶一個引數,該引數是 resolve(res) 返回的資料。

const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('哎呦喂')
},1000)
}).then(res=>{
console.log(res) //1秒後列印哎呦喂
})

Promise.catch

執行 reject 時,Promise 狀態從 pending 變為 rejected,會執行 catch 方法,catch 方法接收的也是一個函式,函式中攜帶一個引數,該引數為 reject(err) 返回的資料。

const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('error message')
},1000)
}).then(res=>{
console.log(res)//不執行
}).catch(err=>{
console.log('err',err)//1秒後列印 error message
})

三、Promise 鏈式呼叫

製作一個模擬網路請求:

  • 第一次返回 a,
  • 修改返回的結果為 aa,作為第二次網路請求返回的結果。
  • 修改結果為 aaa,作為第三次返回結果。
const pp = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
}).then(res=>{
console.log('res1',res) //1秒後列印 a
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(res+'a')
},1000)
})
}).then(res=>{
console.log('res',res) //2秒後列印 aa
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(res+'a')
},1000)
})
}).then(res=>{
console.log('res3',res) //3秒後列印 aaa
})

這種場景其實就是介面的多層巢狀使用,Promise 可以把多層巢狀按照線性的方式進行書寫,非常優雅。我們把 Promise 的多層巢狀呼叫就叫做鏈式呼叫。

上述例項,有三層巢狀就 new 了 3 個Promise,程式碼寫得比較多,我們看看在實現功能的前提下如何能夠簡化。

四、Promise 巢狀使用的簡寫

promise傳入的函式引數reject是一個非必傳的引數,如果不需要處理失敗時的結果時,我們可以省略掉 reject 。程式碼如下:

//簡化1
const ppp = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
}).then(res=>{
console.log('res1',res)
return new Promise(resolve=>resolve(res+'a'))
}).then(res=>{
console.log('res',res)
return new Promise(resolve=>resolve(res+'a'))
}).then(res=>{
console.log('res3',res)
})

Promise 巢狀使用時,內層的 Promise 可以省略不寫,所以我們可以直接把 Promise 相關的去掉,直接返回,程式碼如下:

//簡化2
const pppp = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
}).then(res=>{
return res+'a'
}).then(res=>{
return res+'a'
}).then(res=>{
console.log('res3',res)
})

有的同學就在想,怎麼都是成功狀態的舉例和簡寫,我們的失敗狀態catch可以簡寫嗎?

答案是肯定的,我們簡化為2層巢狀,與上述功能一致。

const ppppp = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('a')
},1000)
}).catch(err=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(err+'a')
},1000)
})
}).catch(err=>{
console.log('err',err)
})

//簡寫1
const pppppp = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('a')
},1000)
}).catch(err=>{
return new Promise((resolve,reject)=>reject(err+'a'))
}).catch(err=>{
console.log('err',err)
})

//簡寫2
const ppppppp = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('a')
},1000)
}).catch(err=>{
throw err+'a'
}).catch(err=>{
console.log('err',err)
})

注意:失敗簡寫省略掉Promise時,使用的 throw 丟擲異常。

五、Promise方法

5.1、all 方法

Promise.all 方法,提供了並行執行非同步操作的能力,並且在所有非同步操作完成之後,統一返回所有結果。具體使用如:

Promise.all([
new Promise(resolve=>resolve('a')),
new Promise(resolve=>resolve('b')),
]).then(res=>{
console.log('all',res)//【'a' , 'b'】
})

all 接收到的是一個數組,陣列長度取決於 Promise 的個數。

一些遊戲類的素材比較多的應用,開啟網頁時,預先載入需要用到的各類資源,所有的都載入完後,再進行頁面的初始化。

5.2、race方法

race翻譯成中文:賽跑。就是誰跑得最快,誰才能觸碰到終點的勝利線。

Promise.race 用法與 all 一樣,只是返回結果上不同,它返回的是執行最快的那個 Promise 的結果。

Promise.race([
new Promise(resolve=>
setTimeout(()=>{
resolve('a')
},100)
),
new Promise(resolve=>
setTimeout(()=>{
resolve('a')
},200)
),
]).then(res=>{
console.log('race',res) // 返回 a
})