JS異步編程之Promise詳解和使用總結

前言

Javascript異步編程能夠算是JS的難點之一。下面就異步編程方法之一的Promise進行詳細介紹和總結。但說到Promise以前,我會簡單提一下什麼是JS異步和回調函數。javascript

JS異步

JS異步是指在進行某些須要耗時不會當即返回結果的操做時,不會阻塞後面的操做,一旦該耗時的操做完成時,則會通知須要調用其結果的函數來作後續處理。這是一種異步非阻塞的操做,也就是說任務的排列順序和執行任務是不一致的。java

回調函數

和同步操做不一樣,異步操做即不會當即返回結果的操做(如發起網絡請求,下載文件,操做數據庫等)。若是咱們後續的函數須要以前返回的結果,又怎樣使以前的異步操做在其完成時通知到後續函數來執行呢?git

一般,咱們能夠將這個函數先定義,存儲在內存中,將其當作參數傳入以前的異步操做函數中,等異步操做結束,就會調用執行這個函數,這個函數就叫作回調函數(callback)。github

若是不用callback,因爲js會當即執行後面console.log,致使打印出來的photoundefined數據庫

var photo = downloadPhoto('http://coolcats.com/cat.gif')
console.log(photo)  //undefined 
複製代碼

使用callback時,咱們將handlePhoto當作callback傳入downloadPhoto這個異步函數中,那麼當圖片下載行爲結束後,不管是成功仍是失敗,都會執行到handlePhoto,對photo或者是error進行處理。編程

downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
function handlePhoto (error, photo) {
  if (error) console.error('Download error!', error)
  else console.log('Download finished', photo)
}
console.log('Download started')
複製代碼

那假如callback函數一樣是個異步函數,且callback裏又嵌入了callback呢? 如此一來,嵌套太深容易引起「回調地獄」,即代碼只會橫向發展,很差管理。json

爲此,對於那些須要連續執行的異步操做,Promise能夠是一種很好的解決辦法。api

Promise

Promise的概念對於初學者來講一直很抽象,咱們能夠舉個例子: 好比你是個經銷商,你要去工廠定貨,拿到貨後你才能本身銷售。那麼你和工廠以前立下一個契約,保證工廠在在完成生產後通知你,或者就算是因某種緣由出錯了而沒法生產也會通知到你。那麼此時這裏的契約就至關於咱們要講述的promise,promise就像是個特殊的對象,鏈接了工廠的生產行爲和你的消費行爲,是生產者和消費者間的紐帶。promise

Promise的建立

Promise建立時,會傳給promise一個稱爲excutor執行器的函數。這個excutor咱們能夠理解爲生產者的生產過程函數。這個函數含有兩個參數resolvereject,這倆參數也一樣是函數,用來傳遞異步操做的結果。語法以下:bash

let promise = new Promise(function(resolve, reject) {
  // executor 
})
複製代碼

有幾點值得說一下:

  1. 在promise對象建立時,excutor會當即執行。
  2. resolvereject是JS引擎自動建立的函數,咱們無需本身建立,只需將其做爲參數傳入就好。
  3. 建立的promise的內部狀態是個對象,初始時爲:
{
   state,  //pending
   result,  //undefined
}
複製代碼

一旦exucutor執行完,要麼產生value,要麼產生error,此時會當即調用resolve(當產生value時)或者調用reject(當產生error)時,內部狀態也會隨之改變,以下圖所示:

注意,當excutor裏面即便調用了多個resolvereject,其最終仍是隻執行一個,其餘的都被忽略掉。

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // ignored
  setTimeout(() => resolve("…")); // ignored
});
複製代碼

Promise的then,catch和finally

在上面例子中,既然生產者的行爲完成了,結果也傳遞出去了,那麼如何通知消費者呢?咱們可使用.then, .catch, 和.finally來註冊消費者的函數,把.then.catch.finally看作是個訂閱列表,將消費者的函數註冊於此,一旦收到結果時,就能夠通知到對方進行相應的處理。

  • .then的用法以下:
promise.then(
result = > resultHandle(result)
error = > errorHandle(error)
)
複製代碼

.then()接收2個函數,一個用來處理正常結果result,一個用來處理error,但通常狀況下,咱們也能夠不用在.then()中傳入這個error處理的函數。

  • .catch的用法以下:
promise.catch(
    error = > errorHandle(error)
)
複製代碼

其實這就是至關於promise.then(null, errorHandle).catch會捕獲到整個異步操做中,或者是一系列連續的異步操做鏈中的出現的任何類型的錯誤,一旦拋出錯誤,則會直接轉入到.catch中進行錯誤處理。

  • .finally的用法以下:
promise.finally(
    finalHandle
)
複製代碼

當promise的狀態肯定時,即不管拿到的是正常結果仍是錯誤信息,總會執行這個finalHandle函數。用.finally能夠作一些清理操做,好比發起網絡請求後,能夠中止loading顯示。

Promise鏈式調用

當有一系列的異步操做須要一個接一個執行時,可使用promise的調用鏈。 舉個例子,咱們用fetch這個方法去發起網絡請求,fetch()返回的是一個promise對象,那麼咱們能夠對其連續地調用.then來進行一步步連續地異步操做。注意:promise.then()返回的是一個新的promise對象,因此咱們才能夠繼續對其調用.then

// Make a request for user.json
fetch('/article/promise-chaining/user.json')
  // Load it as json
  .then(response => response.json())
  // Make a request to github
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  // Load the response as json
  .then(response => response.json())
  // Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
  .then(githubUser => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => img.remove(), 3000); // (*)
  }).catch(error => console.log(error));
複製代碼

這段代碼的做用就是先發起網絡請求獲取到服務端的相應內容(其實只是響應頭),而後經過調用response.json()繼續獲取response完整的遠程數據並將其解析爲JSON格式(也是異步操做),接着根據json中user的name信息,繼續發起網絡請求,拿到用戶object及其頭像url,展現其頭像並在3秒後刪除頭像圖片。.catch會處理上面一系列流程中出現的任何錯誤。

值得注意的是,promise.then( handleFunction ) 中的handleFunction能夠返回當即值,也能夠返回promise對象。若是返回當即值,則能夠直接把結果傳入到下一步的.then進行處理,可是若是返回的是promise對象,那麼必定要等到這個返回的promise處理完,拿到結果後,纔會進行下一步的.then處理!能夠用下圖加以理解:

總結

在JS異步編程中,Promise相對於callback,具備更優的代碼流,而且具備很好的靈活性。Promise符合天然的事物執行順序,即先作異步操做,而後再用.then告知下一步該作什麼。而在Callback的用法中,先得知道下一步作什麼,而後才能將其做爲callback函數傳入異步操做函數中。並且,promise在獲得結果後,能夠通知到多個後續的結果處理函數,.then就像一個訂閱列表同樣。而在callback的用法中,只能傳入1個callback函數。

參考連接

Promise: javascript.info/promise-bas…

Promise MDN文檔: developer.mozilla.org/en-US/docs/…

Callback Hell:callbackhell.com/

Fetch: developer.mozilla.org/en-US/docs/…

相關文章
相關標籤/搜索