你想了解的Promise,都在這裏!!

前言

在JavaScript語言中,代碼都是是單線程執行的,正是因爲這個緣由,致使了JavaScript中全部的網絡操做,瀏覽器事件,都必須知足異步執行的要求。因此異步的各類方案開始出現並逐步合理化,簡單話!api

異步處理

在開發過程當中你們使用的異步處理方案通常包括:回調函數(Callback)PromiseGenerator函數、async/await。這裏就主要說一下這些方案的異同:數組

回調函數(Callback)

假設咱們定義一個getData函數用於數據請求:promise

function getData(url, callback) {
  // 模擬數據請求
  setTimeout(() => {
    let res = {
      url: url,
      data: {}
    }
    callback(res)
  }, 1000)
}
複製代碼

如今的需求是咱們須要依次請求三次服務器,而且每次請求的數據必須在上次成功的基礎上執行:瀏覽器

getData('/api/page/1?params=123',(res1) => {
  console.log(res1);
  getData(`/api/page/2?params=${res1.data.params}`, (res2) => {
    console.log(res2);
    getData(`/api/page/3?params=${res2.data.params}`, (res3) => {
      console.log(res3);
    })
  })
})
複製代碼

經過上面的🌰,咱們能夠看到第一次的url:/api/page/1?params=123,第二次的url: /api/page/2?params=${res1.data.params},依賴第一次請求的數據,第三次的url:/api/page/2?params=${res2.data.params},依賴第二次請求的數據。因爲咱們每次的數據請求都依賴上次的請求,因此咱們將會將下一次的數據請求以回調函數的形式寫在函數內部,這其實就是咱們常說的回掉地獄服務器

Promise

一樣的需求,咱們使用Promise,去實現看看:網絡

首先咱們須要先將咱們的getData函數改寫成Promise的形式異步

function getDataPromise(url) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = {
          url: url,
          data: {}
        }
        resolve(res)
      }, 1000)
    })
  }
複製代碼

那麼邏輯代碼應該變成:async

getDataPromise('/api/page/1?params=123')
    .then(res1 => {
      console.log(res1);
      return getDataPromise(`/api/page/2?params=${res1.data.params}`)
    })
    .then(res2 => {
      console.log(res2);
      return getDataPromise(`/api/page/3?params=${res2.data.params}`)
    })
    .then(res3 => {
      console.log(res3);
    })
複製代碼

這樣寫完來看,發現咱們每次在數據請求成功(then)以後返回一個Promise對象,方便下次使用,這樣咱們就避免了回掉地獄的出現,可是這樣其實也不算事完美,當咱們的請求變得複雜的時候咱們會發現咱們的代碼會變的更加複雜。函數

爲了不這種狀況的出現 async/await應運而生。ui

async/await

getData函數不變,仍是Promise

function getDataPromise(url) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = {
          url: url,
          data: {}
        }
        resolve(res)
      }, 1000)
    })
  }
複製代碼

需求代碼變成:

async function getData () {
    let res1 = await getDataPromise('/api/page/1?params=123');
    console.log(res1);
    let res2 = await getDataPromise(`/api/page/2?params=${res1.data.params}`);
    console.log(res2);
    let res3 = await getDataPromise(`/api/page/2?params=${res2.data.params}`);
    console.log(res3);
  }

複製代碼

怎麼樣,是否是這段代碼閱讀起來很是舒服,其實async/await都是基於Promise的,使用async方法最後返回的仍是一個Promise;實際上async/await能夠看做是Generator異步處理的語法糖,👇咱們就來看一下使用Generator怎麼實現這段代碼

Generator

// 異步函數依舊是Promise
  function getDataPromise(url) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = {
          url: url,
          data: {}
        }
        resolve(res)
      }, 1000)
    })
  }

  function * getData() {
    let res1 = yield getDataPromise('/api/page/1?params=123');
    console.log(res1);
    let res2 = yield getDataPromise(`/api/page/2?params=${res1.data.params}`);
    console.log(res2);
    let res3 = yield getDataPromise(`/api/page/2?params=${res2.data.params}`);
    console.log(res3);
  }

複製代碼

其實能夠分開來看:

let fn = getData()
  fn.next().value.then(res1 => {
    fn.next(res1).value.then(res2 => {
      fn.next(res2).value.then( () => {
        fn.next()
      })
    })
  })

複製代碼

上面的代碼咱們能夠看到,next()每一步之行.value方法返回的都是一個Promise,因此咱們能夠在後面添加then方法,在then方法後面我繼續調用next(),知道函數運行完成。實際上上面的代碼咱們不須要手動去寫,咱們能夠對其封裝一下:

function run(gen) {
    let fn = gen()

    function next(data) {
      let res = fn.next(data)
      if (res.done) return res.value
      res.value.then((info) => {
        next(info)
      })
    }
    next()
  }

  run(getData)
複製代碼

run方法用來自動執行一步操做,其實就能夠看做是Generator在進行遞歸操做;

這樣咱們就將異步操做封裝到了函數內部,其實不難發現async/awaitGenerator有不少類似的地方,只不過async/await在語義上更容易被理解。

在使用async/await的時候咱們不須要在去定義run(),它內部已經給咱們定義封裝好了,這也是爲何說async/awaitGenerator異步處理的語法糖了。

Promise

上面咱們介紹了回調函數(Callback)PromiseGenerator函數、async/await的區別,下面咱們就來具體說說Promise

Promise.prototype.then()

  • 做用

thenPromise.prototype.catch() 方法都會返回 promise,它們能夠被鏈式調用 — 一種稱爲複合composition 的操做.

  • 參數

第一個參數:狀態從 pending -> fulfilled 時的回調函數

第二個參數:狀態從 pending -> rejected 時的回調函數

  • 返回值:新的 Promise 實例(注意不是原來的 Promise 實例

  • 特色

因爲 then 方法返回一個新的 Promise 實例,因此 then 方法是能夠鏈式調用的,鏈式調用的 then 方法有兩個特色:

第一:後一個 then 方法的回調函數的參數是前一個 then 方法的返回值

第二:若是前一個 then 方法的返回值是一個 Promise 實例,那麼後一個 then 方法的回調函數會等待該 Promise 實例的狀態改變後再執行

Promise.prototype.catch

  • 描述

catch 方法能夠用於您的promise組合中的錯誤處理。

Internally calls Promise.prototype.then on the object upon which is called, passing the parameters undefined and the onRejected handler received; then returns the value of that call (which is a Promise).

你們能夠看一下下面的代碼:

const promise = new Promise(function (resolve, reject) {
    setTimeout(() => {
        reject('err')
    }, 1000)
})

promise.then(
    res => console.log('s1'),
    err => console.log('e1')
).then(
    res => console.log('s2')
).catch(
    err => console.log('e2')
)
複製代碼
e1
s2
複製代碼

能夠發現,在第一個 then 方法執行的錯誤處理函數中捕獲到了錯誤,因此輸出了 e1,那麼這個錯誤已經被捕獲到了,也就不須要 catch 再次捕獲了,因此沒有輸出 e2,這是正常的,但問題是居然輸出了 s2。。。。 因此爲了不這種狀況代碼應該改成:

promise.then(
    res => console.log('s1')
).then(
    res => console.log('s2')
).catch(
    err => console.log('e2')
)
複製代碼

這樣只會輸出e2

Promise.prototype.finally

當咱們想在Promise不管成功仍是失敗的時候都想進行某一步操做時,能夠說使用finally

promise.then(
    res => console.log('s1')
).catch(
    err => console.log('e1')
).finally(
    () => console.log('end')
)
複製代碼

很容易可以發現,.finally 只不過是一個成功與失敗的回調函數相同的 .then 而已。

Promise.all(iterable);

  • 參數(iterable) 一個可迭代的對象,如 Array 或 String;

  • 返回值

    • 若是傳入的參數是一個空的可迭代對象,則返回一個已完成(already resolved)狀態的 Promise。
    • 若是傳入的參數不包含任何 promise,則返回一個異步完成(asynchronously resolved) Promise。注意:Google Chrome 58 在這種狀況下返回一個已完成(already resolved)狀態的 Promise。
    • 其它狀況下返回一個處理中(pending)的Promise。這個返回的 promise 以後會在全部的 promise 都完成或有一個 promise 失敗時異步地變爲完成或失敗。 見下方關於「Promise.all 的異步或同步」示例。返回值將會按照參數內的 promise 順序排列,而不是由調用 promise 的完成順序決定。

🌰:

const p = Promise.all([promise1, promise2, promise3])

  p.then(
      (res) => {
          // res 是結果數組
      }
  )
複製代碼

只有當全部 Promise 實例的狀態都變爲 fulfilled,那麼 Promise.all 生成的實例纔會 fulfilled。 只要有一個 Promise 實例的狀態變成 rejected,那麼 Promise.all 生成的實例就會 rejected

Promise.race

  • 做用:與 Promise.all 相似,也是將多個 Promise 實例包裝成一個 Promise 實例。

  • 參數:與 Promise.all 相同

  • 特色:

Promise.race 方法生成的 Promise 實例的狀態取決於其所包裝的全部 Promise 實例中狀態最早改變的那個 Promise 實例的狀態。

race 函數返回一個 Promise,它將與第一個傳遞的 promise 相同的完成方式被完成。它能夠是完成( resolves),也能夠是失敗(rejects),這要取決於第一個完成的方式是兩個中的哪一個。 若是傳的迭代是空的,則返回的 promise 將永遠等待。 若是迭代包含一個或多個非承諾值和/或已解決/拒絕的承諾,則 Promise.race 將解析爲迭代中找到的第一個值。

  • 例子:請求超時
const promise = Promise.race([
    getData('/path/data'),
    new Promise((resolve, reject) => {
        setTimeout(() => { reject('timeout') }, 10000)
    })
])

promise.then(res => console.log(res))
promise.catch(msg => console.log(msg))
複製代碼

Promise.resolve()

  • 做用:將現有對象(或者原始值)轉爲 Promise 對象。

  • 參數:參數能夠是任意類型,不一樣的參數其行爲不一樣

    • 若是參數是一個 Promise 對象,則原封不動返回
    • 若是參數是一個 thenable 對象(即帶有 then 方法的對象),則 Promise.resolve 會將其轉爲 Promise 對象並當即執行 then 方法
    • 若是參數是一個普通對象或原始值,則 Promise.resolve 會將其包裝成 Promise 對象,狀態爲 fulfilled
    • 不帶參數,則直接返回一個狀態爲 fulfilledPromise 對象

Promise.reject()

  • 概述

Promise.reject(reason)方法返回一個帶有拒絕緣由reason參數的Promise對象。

通常經過使用Error的實例獲取錯誤緣由reason對調試和選擇性錯誤捕捉頗有幫助。

  • 參數:任意參數,該參數將做爲失敗的理由:
Promise.reject('err')

// 等價於
new Promise(function (resolve, reject) {
    reject('err')
})
複製代碼

統一使用Promise

其實咱們在js中能夠將同步代碼也可以使用Promise

function a() {
  console.log('aaa')
}

// 等價於
const p = new Promise((resolve, rejext) => {
  resolve(a())
})
複製代碼

That's All

或者點擊Promise

相關文章
相關標籤/搜索