亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

ES6+ Promise 基礎

1. 前言

我們知道瀏覽器在渲染網頁時,會創建一個渲染進程進行渲染頁面,在渲染進程中其中有 GUI 渲染線程和 JS 引擎線程(如 V8 引擎)兩個線程是互斥的。也就是說在同一時間內只能有一個線程執行。如果 JavaScript 執行一段耗時程序時會阻止頁面渲染。如果要頁面快速在用戶面前呈現就要做一些優化處理。對于不能立馬得到結果的程序,不需要等待,可以放到事件隊列中,等到得到結果后再執行。

對這種不等待方式,JavaScript 提供了異步的解決方案,在 JavaScript 中常見的異步解決方案是 Callback 方式,而像 setTimeout 這樣提供異步的 API,還可以使用發布訂閱的來實現異步。使用回調函數存在回調地獄的問題。為了解決回調地獄,最早社區提出了 Promise 概念。最后在 ES6 時正式作為官方的解決方案,說明 Promise 有它獨有的優勢。本節我們將學習 Promise 的基本用法。

2. 回調地獄

我們都知道 JavaScript 異步使用的是回調函數,下面我們來看一個 ajax 請求的實例,下面的 ajax 方法是一個偽代碼,可以看作是請求接口的方法,接口請求的庫可以參考 jQuery 的 $.ajax 和 axios。

// ajax請求的偽代碼
function ajax(url, sucessCallback, failCallback) {
  // url:請求的url
  // sucessCallback:成功的回調函數
  // failCallback:失敗的回調函數
}

ajax(url1, (res1) => {
  ajax(url2, (res2) => {
    ajax(url3, (res3) => {
		doSomething(res1, res2, res3)
    })
  })
})

上面的 ajax 請求我們可以理解為,在調用 doSomething 方法時需要前面三個請求的結果作為參數,所以只有前一個 ajax 請求得到結果后才能發起第二個請求。這樣前后有依賴的嵌套被稱為回調地獄。對于比較復雜邏輯的情況來說,回調地獄會使程序出問題的概率大大增加。

另外,這樣做有個很嚴重的問題,就是接口請求的時間是三個請求的和,不能進行并發操作,當然我們也可以做一些優化操作,如下:

let out = after(3, function (data){
  doSomething(...data)
})

ajax(url1, (res1) => {
  out(res1)
})
ajax(url2, (res2) => {
  out(res2)
})
ajax(url3, (res3) => {
  out(res3)
})

function after(times, callback) {
  const arr = [];
  return function (value){
    arr.push(value);
    if (--times==0) {
      callback(arr);
    }
  }
}

上面的代碼很優雅地解決了回調嵌套的問題,但同時我們需要手動維護一個計數器來控制最后的回調。這無疑增加了程序的復雜度,我們更希望的是關注我的業務,而不是寫更多的邏輯來優化。

針對這種情況,社區提供了很多這類優化的庫,而 Promise 則是其中最亮眼的。對上面的情況,Promise 怎么解決的呢?看如下的實現方式:

function request(url) {
  return new Promise((resolve, reject) => {
    ajax(url, (res) => {
      resolve(res)
    })
  })
}
Promise.all([request(url1), request(url1), request(url1)]).then((result) => {
  doSomething(...result)
}).catch((error) => {
  console.log(error)
})

上面的代碼中我們封裝了一個 request 請求的方法,通過 Promise.all() 來并發請求這些接口,當接口都正確返回才會執行 then 方法中的回調,有一個錯誤都會拋出異常。這種方式比較好的是,我們對請求進行了封裝,不要再關注每一步請求是否完成做對應的邏輯處理,讓我們在開發過程中更加關注業務邏輯,使開發效率更快。

3. Promise 用法

前面我們通過一個回調地獄的案例,說明了 Promise 的優點,就是為了解決異步而產生的。并且可以處理并發請求,很好地優化了程序資源。

3.1 實例化一個 Promise

首先需要明確 Promise 是一個類,我們在 VSCode 中輸入 new Promise() 會給我們如下的提示:

圖片描述

new Promise() 時需要默認需要傳入一個回調函數,這個回調函數是 executor(執行器),默認會立即執行。執行器會提供兩個方法(resolve 和 reject)用于改變 promise 的狀態。resolve 會觸發成功狀態,reject 會觸發失敗狀態,無論成功或失敗都會傳入一個返回值,這個返回值會在實例調用 then 方法后作為響應值獲取。

var promise = new Promise((resolve, reject) => {
  ajax(url, (data) => {
    resolve(data)	// 成功
  }, (error) => {
    reject(error)	// 失敗
  })
})

上面的代碼中實例化一個 ajax 請求的 Promise, 當接口請求成功就會調用 resolve () 方法把請求的值傳入進去,如果失敗了就調用 reject () 方法把錯誤信息傳入進去。在后續的鏈式調用中獲取相應的結果。

我們需要知道的是,Promise 有三個狀態:等待(pending)、成功(fulfilled),失敗(rejected)。在初始化時,這個狀態是等待態,在等待狀態時可以轉化為成功態或失敗態。當狀態是成功態或是失敗態后不能再被改變了。

上面的代碼中可以改變 Promise 狀態的是執行器提供的 resolve 和 reject,resolve 會將等待態變為成功態,reject 則會將等待態變為失敗態,在狀態變為成功或失敗的狀態時就不能被更改了。

3.2 then

在實例化 Promise 類后,我們如何訪問成功和失敗的值呢?Promise 提供了鏈式調用的 then 方法用于訪問成功的值和失敗的值,then 提供了兩個回調 onfulfilled(成功回調)、onrejected(失敗回調)。

var promise = new Promise((resolve, reject) => {
  resolve(123);
  // reject('error')
  // throw new Error('Error')
})

promise
	.then(
    (data) => {
      console.log(data)	// 123
      return '100'
    },
    (reason) => {
      console.log(reason)
    }
  )
  .then((data) => {
      console.log(data)	// 100
  }, null)

上面的代碼中給我了幾個測試用例,有興趣的小伙伴可以進行測試。then 方法返回一個值而不是 Promise 實例,并且會把這個結果返回到下一個 then 的成功回調中;

如果返回的是一個 promise,下一個 then 會采用這個 promise 結果,如果返回的是失敗,會傳到下一個 then 中失敗的回調函數中去:

var promise = new Promise((resolve, reject) => {
  resolve(123);
})

promise
	.then(
    (data) => {
      return new Promise((resolve, reject) => {
        reject('錯誤內容');
      })
    },
    null
  )
  .then(null, (err) => {
      console.log('error:', err)	// error: 錯誤內容
  })

如果在失敗的回調函數中返回一個普通值或成功的 promise 也會走到下一層 then 的成功回調中去。

promise.then(null, (err) => {
	return '100';
}).then((data) => {
  console.log('data:', data);	// data: 123
}, null)

通過上面的例子可以知道,當前 then 中走成功與否,主要看上一層返回的結果??偨Y有兩點。

  • 當上一層返回一個普通值,或是一個成功的 Promise,則會走到下一層成功的回調函數中去;
  • 如果上一層返回一個失敗的 Promise,或是拋出一個異常,則會走到下一層的失敗的回調函數中去。

4. 小結

本節主要通過 JavaScript 中回調地獄的一個案例來引出為什么使用 Promise,以及 Promise 所帶來的好處。然后學習了 Promise 的基本使用和鏈式調用 then 方法,需要注意的是,then 中執行成功或是失敗是根據它上一層的返回值,如果返回的是一個普通值或成功的 Promise 則會走 then 的成功回調;如果拋出異?;蚍祷厥〉?Promise 則走 then 的失敗回調。