Promise: 給我一個承諾,我還你一個承諾

爲什麼產生promise?

在 Promise 出現之前,咱們處理一個異步網絡請求,需求大概是這樣:咱們須要根據第一個網絡請求的結果,再去執行第二個網絡請求,再根據第二個網絡請求的結果去執行第三個請求~,需求是永無止境的,因而乎出現了以下代碼:javascript

請求1(function(){
    // 一些其餘操做
    請求2(function(請求1結果){
        // 一些其餘操做
        請求3(function(請求2結果){
            // 一些其餘操做
            請求4(function(請求3結果){
                // 一些其餘操做
                請求5(function(請求4結果){
                    // 一些其餘操做
                    請求6(function(請求5結果){
                        // 一些其餘操做
                        ...
                    })
                })
            })
        })
    })
})
複製代碼

這就是所謂的回調地獄,回調地獄帶來的負面做用有如下幾點:java

  • 代碼臃腫。
  • 可讀性差。
  • 耦合度太高,可維護性差。
  • 代碼複用性差。
  • 容易滋生 bug。
  • 只能在回調裏處理異常。

這時,就有人思考了,能不能用一種更加友好的代碼組織方式,解決異步嵌套的問題。因而 Promise 規範誕生了,promise採用鏈式調用,很好地解決了回調地獄的痛點。編程

咱們使用Promise常規寫法來實現上面的異步網絡請求,代碼以下:數組

new Promise(請求1)
    .then(請求2(請求結果1))
    .then(請求3(請求結果2))
    .then(請求4(請求結果3))
    .then(請求5(請求結果4))
    .then(請求6(請求結果5))
    .catch(處理異常(異常信息))複製代碼

咱們不難發現,Promise 的寫法更爲直觀,而且可以在外層捕獲異步函數的異常信息。promise

什麼是promise?

Promise 是異步編程的一種解決方案,比傳統的解決方案【回調函數】和【事件】更合理、更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。Promise說得通俗一點就是一種寫代碼的方式,而且是用來寫JavaScript編程中的異步代碼的。bash

promise三種狀態

三種狀態:網絡

  • pending:進行中
  • fulfilled :已成功
  • rejected 已失敗

只有異步操做的結果才能肯定當前處於哪一種狀態,任何其餘操做都不能改變這個狀態,這也是Promise(承諾)的由來。dom

Promise對象的狀態改變,只有兩種可能:異步

  • 從pending變爲fulfilled
  • 從pending變爲rejected

這兩種狀況只要發生,狀態就凝固了,不會再變了,這時就稱爲resolved(已定型)異步編程

promise缺點

  1. 沒法取消Promise,一旦新建它就會當即執行,沒法中途取消
  2. 若是不設置回調函數(沒有捕獲錯誤),Promise內部拋出的錯誤,不會反應到外部
  3. 當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)

promise API

咱們先把Promise打印出來,會發現Promise是一個構造函數,本身身上有all、reject、resolve、race等方法,原型上有then、catch、finally等方法。

【1】Promise.prototype.constructor()

它的基本用法以下:

let promise = new Promise((resolve, reject) => {
      // 在這裏執行異步操做
      if (/*異步操做成功*/) {
        resolve(success)
      } else {
        reject(error)
      }
    })複製代碼

Promise接收一個函數做爲參數,函數裏有resolve和reject兩個參數:

  1. resolve方法的做用是將Promise的pending狀態變爲fulfilled,在異步操做成功以後調用,能夠將異步返回的結果做爲參數傳遞出去。
  2. reject方法的做用是將Promise的pending狀態變爲rejected,在異步操做失敗以後調用,能夠將異步返回的結果做爲參數傳遞出去。

他們之間只能有一個被執行,不會同時被執行,由於Promise只能保持一種狀態。

【2】Promise.prototype.then()

Promise實例肯定後,能夠用then方法分別指定fulfilled狀態和rejected狀態的回調函數。它的基本用法以下:

promise.then((success) => {
      // 異步操做成功在這裏執行
      // 對應於上面的resolve(success)方法
    }, (error) => {
      // 異步操做失敗在這裏執行
      // 對應於上面的reject(error)方法
    })

    // 還能夠寫成這樣 (推薦使用這種寫法)
    promise.then((success) => {
      // 異步操做成功在這裏執行
      // 對應於上面的resolve(success)方法
    }).catch((error) => {
      // 異步操做失敗在這裏執行
      // 對應於上面的reject(error)方法
    })複製代碼

then(onfulfilled,onrejected)方法中有兩個參數,兩個參數都是函數,第一個參數執行的是resolve()方法(即異步成功後的回調方法),第二參數執行的是reject()方法(即異步失敗後的回調方法)(第二個參數可選)。它返回的是一個新的Promise對象。

【3】Promise.prototype.catch()

catch方法是.then(null,onrejected)的別名,用於指定發生錯誤時的回調函數。做用和then中的onrejected同樣,不過它還能夠捕獲onfulfilled拋出的錯,這是onrejected所沒法作到的:

function createPromise(p, arg) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (arg === 0) {
            reject(p + ' fail')
          } else {
            resolve(p + ' ok')
          }
        }, 0);
      })
    }

    createPromise('p1', 1).then((success) => {
      console.log(success) // p1 ok
      return createPromise('p2', 0)
    }).catch((error) => {
      console.log(error) // p2 fail
    })

    createPromise('p1', 1).then((success) => {
      console.log(success) // p1 ok
      return createPromise('p2', 0)
    }, (error) => {
      console.log(error) // Uncaught (in pomise) p2 fail
    })
複製代碼


Promise錯誤具備"冒泡"的性質,若是不被捕獲會一直往外拋,直到被捕獲爲止;而沒法捕獲在他們後面的Promise拋出的錯。

【4】Promise.prototype.finally()

finally方法用於指定無論Promise對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入的標準:

createPromise('p1', 0).then((success) => {
      console.log(success)
    }).catch((error) => {
      console.log(error) // p1 fail
    }).finally(() => {
      console.log('finally') // finally
    })

    createPromise('p1', 1).then((success) => {
      console.log(success) // p1 ok
    }).catch((error) => {
      console.log(error)
    }).finally(() => {
      console.log('finally') // finally
    })複製代碼

finally方法不接受任何參數,故可知它跟Promise的狀態無關,不依賴於Promise的執行結果。

【5】Promise.all()

Promise.all方法接受一個數組做爲參數,但每一個參數必須是一個Promise實例。Promise的all方法提供了並行執行異步操做的能力,而且在全部異步操做都執行完畢後才執行回調,只要其中一個異步操做返回的狀態爲rejected那麼Promise.all()返回的Promise即爲rejected狀態,此時第一個被reject的實例的返回值,會傳遞給Promise.all的回調函數:

function createPromise(p, arg) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (arg === 0) {
            reject(p + ' fail')
          } else {
            resolve(p + ' ok')
          }
        }, 0);
      })
    }

    // test: 兩個Promise都成功
    Promise.all([createPromise('p1', 1), createPromise('p2', 1)])
      .then((success) => {
        console.log(success) // ['p1 ok', 'p2 ok']
      }).catch((error) => {
        console.log(error)
      })

    // test: 其中一個Promise失敗
    Promise.all([createPromise('p1', 0), createPromise('p2', 1)])
      .then((success) => {
        console.log(success)
      }).catch((error) => {
        console.log(error) // p1 fail 
      })

    // test: 兩個Promise都失敗
    Promise.all([createPromise('p1', 0), createPromise('p2', 0)])
      .then((success) => {
        console.log(success)
      }).catch((error) => {
        console.log(error) // p1 fail 只打印第一個失敗的異步操做信息
      })
複製代碼

【6】Promise.race()

Promise的race方法和all方法相似,都提供了並行執行異步操做的能力。顧名思義,race就是賽跑的意思,意思就是說Promise.race([p1, p2, p3])裏面哪一個結果得到的快,就返回那個結果,無論結果自己是成功狀態仍是失敗狀態,如下就是race的執行過程:

let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    })
 
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('failed')
      }, 500)
    })
 
    Promise.race([p1, p2]).then((success) => {
      console.log(success)
    }).catch((error) => {
      console.log(error)  // failed
    })
複製代碼

【7】Promise.resolve()

有時須要將現有對象轉爲 Promise 對象Promise.resolve()方法就起到這個做用。

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))複製代碼

【8】Promise.reject()

Promise.reject()方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。

const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))複製代碼

例子

Promise 翻譯成中文爲「承諾, 諾言」, 例如: 你承諾這個月掙錢了給你老婆買一個包, 那麼你先去掙錢, 等掙錢了就馬上給老婆買包, 實現你的諾言, 沒掙到錢就立馬道歉。換成代碼就是:

// 買包就是一個Promise,Promise的意思就是承諾
  // 這時候老公給老婆一個承諾
  // 在將來的一個月,無論掙沒掙到錢,都會給老婆一個答覆
  
  let buyBag = new Promise((resolve, reject) => {
    // Promise 接受兩個參數
    // resolve: 異步事件成功時調用(掙到錢)
    // reject: 異步事件失敗時調用(沒掙到錢)

    // 模擬掙錢機率事件
    let result = function makeMoney() {
      return Math.random() > 0.5 ? '掙到錢' : '沒掙到錢'
    }

    // 下面老公給出承諾,無論掙沒掙到錢,都會給老婆一個答覆
    if (result == '掙到錢')
      resolve('我買包了')
    else
      reject('很差意思,我這個月沒掙到錢')
  })


  buyBag().then(res => {
    // 返回 "我買包了"
    console.log(res)
  }).catch(err => {
    // 返回 "很差意思,我這個月沒掙到錢"
    console.log(err)
  })複製代碼

解釋一下

第一段調用了Promise構造函數,第二段是調用了promise實例的.then方法

1. 構造實例

  • 構造函數接受一個函數做爲參數
  • 調用構造函數獲得實例buyBag的同時,做爲參數的函數會當即執行
  • 參數函數接受兩個回調函數參數resolve和reject
  • 在參數函數被執行的過程當中,若是在其內部調用resolve會將buyBag的狀態變成fulfilled,或者調用reject會將buyBag的狀態變成rejected

2. 調用.then

  • 調用.then能夠爲實例buyBag註冊兩種狀態回調函數
  • 當實例buyBag的狀態爲fulfilled,會觸發第一個回調函數執行
  • 當實例buyBag的狀態爲rejected,則觸發第二個回調函數執行
相關文章
相關標籤/搜索