大白話透徹講解 Promise 的使用,讀完你就懂了
一、為什麼使用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 })