大白話透徹講解 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
})