JavaScript之Promise

寫東西真是難啊,一是因爲肚子裏沒多少貨,二是因爲掌握的東西也太細碎表面。好吧,這也是爲什麼要寫的原因,希望能借此倒逼一下自己。

本來想着一個主題接一個主題的來寫,但是發現好難。好吧,那就從自己熟悉的東西開始寫。

Promise長啥樣

先看大致的代碼

new Promise(function (resolve, reject) {
  // 執行異步操作......

  // 判斷異步操作的結果
  if (/* success */) {
    resolve(value);
  } else {
    reject(err);
  }
}).then(res => {
  // 成功,執行後續操作
}).catch(err => {
  // 失敗,處理錯誤
})

Promise的字面意思就是承諾,承諾表示表示將要做但是還未做的事情,而且承諾還伴隨着這件事情是成功或者失敗的反饋。所以當new一個Promise的時候,就相當於告訴js引擎去幫我們執行一些操作(同步或者異步的都可以),執行完後再告訴我們結果,然後我們再根據結果的成功與否去做之後的操作。

舉個栗子

console.log('start...')
new Promise(function (resolve, reject) {
  console.log('start promise...')
  // 大約0.5秒後執行
  setTimeout(() => {
    console.log('start timeout...')
    // 隨機生成一個num,1 <= num <= 10
    const num = parseInt(Math.random() * 10) + 1
    // 大於5算成功,否則算失敗
    if (num > 5) {
      resolve('success')
    } else {
      reject('failed')
    }
    console.log('end timeout...')
  }, 500)
  console.log('end promise...')
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})
console.log('end...')

運行結果:

start...
start promise...
end promise...
end...
start timeout...
end timeout...
failed/success

這段代碼,首先會打印出start...字符串,然後當我們new Promise()的時候,引擎會立刻執行Promise裏的代碼,所以打印出start promise...。緊接着,遇到setTimeout,所以直接跳到end priomise...處打印該字符串。這個時候,Promise裏的代碼暫時執行完畢,引擎將切換到Promise外部執行後續的同步代碼,打印出end...。大約在0.5秒後,引擎將切回Primise內部執行setTimeout的回調函數,於是打印出start timeout...,但是不管resolve還是reject,只要沒有return語句,都將打印出end promise...,最後纔是執行then或者catch裏的回調函數。

Promise的3種狀態

Promise對象一共有pedding(進行中),fulfilled(已成功)和rejected(已失敗)3種狀態。從創建Promise到resolve函數執行之前,都是pedding狀態,resolve()執行後變成fulfilled狀態,reject()執行後變成rejected狀態。

Promise的then方法

Promise的then方法是在Promise的狀態改變時調用,接收兩個參數,第一個參數是成功時的回調函數,第二個參數(可選)是失敗時的回調函數。

then方法會返回一個新的Promise實例,於是我們可以繼續在then後面寫then(catch方法同樣返回Promise實例,可以鏈式調用)。這個特性挺有用的,下面用示例說一下。

首先,先創建一個函數,該函數返回一個Promise實例,該Promise成功或失敗的概率各50%。

const isMoreThanFive = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      const num = parseInt(Math.random() * 10) + 1
      if (num > 5) {
        resolve('success')
      } else {
        reject('failed')
      }
    }, 500)
  })
}

一個then函數的情況:

isMoreThanFive()
.then((res) => {
  console.log(res)
})
.catch(err => {
  console.log(err)
})
// 0.5秒後輸出success或者failed

兩個then函數的情況:

isMoreThanFive()
.then((res) => {
  console.log('first ' + res);
  return isMoreThanFive()
})
.then(res => {
  console.log('second ' + res);
})
.catch(err => {
  console.log(err)
})

這種情況,如果第一個isMoreThanFive執行失敗,將直接執行catch回調,如果成功,會執行第一個then方法,then方法裏又通過isMoreThanFive返回了一個新的Promise實例,這時,如果新的Promise成功將執行第二個then方法,如果失敗將執行catch方法。

末尾的catch方法會捕獲到前面Promise出現的所有異常,包括reject返回的失敗狀態和代碼執行過程中發生的錯誤,所以一般會省略then方法的第二個參數(處理失敗時的回調),而直接在最後添加catch方法統一處理前面發生的錯誤。
在調用接口時,如果B接口依賴A接口返回的參數,這時,then的鏈式調用將非常方便。

Promise的all方法

先看代碼

const p1 = isMoreThanFive()
const p2 = isMoreThanFive()
Promise.all([p1, p2])
.then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err)
})  
// 輸出 failed 或者 ['success', 'success']

all方法接受一個數組,數組各項都是Promise實例。Promise.all會返回一個新的Promise實例,只有當數組內所有的Promise實例都成功時,all才返回成功狀態,否則只要有一個失敗都返回失敗狀態。如果all執行成功,將返回一個數組,數組的值和Promise實例的返回值一一對應。

注意,傳給all方法的所有Promise實例都是同時執行的,也就是說同時執行多個異步操作,如果某個實例失敗,則all方法直接返回失敗狀態,返回值由失敗的Promise實例提供。

這在前端渲染頁面時非常有用,利用Promise.all方法將可以同時請求多個接口,提升頁面的渲染速度。

特殊情況,當Promise實例有自己的catch方法時:

const p1 = isMoreThanFive()
const p2 = isMoreThanFive()
          .catch(err => {
            return err
          })
Promise.all([p1, p2])
.then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err)
})  
// 輸出 failed 或者 ['success', 'success'] 或者 [ 'success', 'failed' ]

如果傳入的Promise實例有自己的catch方法,就不會觸發Promise.all的catch方法。p2如果返回失敗,會被自己的catch方法捕獲,經過catch處理後返回一個resolved成功的Promise實例,返回值是失敗狀態返回的err,最後Promise.all也就返回成功狀態。

最後

單獨使用Promise已經很方便很直觀了,但是如果配合上async函數的話,寫異步代碼完全可以使用同步的寫法,下一篇將會寫關於async的東西。

關於Promise,大致就是這些,沒有面面俱到,主要是把自己覺得經常用到的和重要的寫了下來。如果想了解更多的細節和原理可以看阮一峯老師的ES6教程。

圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章