【翻譯】如何在React中使用async/await (componentDidMount Async)

PeterYuan發表於2019-03-04

最近更新:瓦倫提諾.加格利亞提 2018年4月30號 原文連結

如何在React中使用async/await

pic1

你想像在NodeJS中使用async/await 那樣在React中同樣使用它們?

create-react-app 構建的專案支援開箱即用。

但是如果你想在自己搭建的webpack配置的專案中使用,你可能會遇到 regeneratorRuntime is not defined 的異常錯誤。

如果你遇到了這個錯誤還想在React中使用async/await,白日做夢。

在此,我們將按部就班的實現:

  • 修復 regeneratorRuntime is not defined 異常報錯
  • 結合Fetch在React中使用async/await
  • 結合Fetch使用async/await處理異常

準備好了嗎?

(以下均是原文連結,緊接著會有翻譯)

  1. 什麼是async/await
  2. 一個Promise的例子
  3. 使用async/await語法規則
  4. 處理異常
  5. 封裝
  6. FAQ

什麼是async/await

首先我們先簡單介紹一下async/await.

async/await 只是JS中Promise的語法糖而已。

啥?JS裡的Promise還不夠你折騰?

Promise固然很好,但是某些情況下你會以長長的then/catch鏈告終。

我想說的是,如果你發覺自己在這樣的處境內也很好,起碼你簡化了程式碼。

但是,async/await 可以以一種方式幫助你像編寫同步程式碼那樣的編寫非同步程式碼。

這會使得你的程式碼更加整潔和可讀,且你還可以使用try/catch去合理的處理異常。

async/await 既方便又整潔:在某個種層面上你會想要把它用到你的React元件內。

讓我們看看如何實現。

一個Promise的例子

從克隆我的webpack倉庫開始(這是一個基於webpack V4 的包含了開箱即用的React的快速開始專案):

git clone git@github.com:valentinogagliardi/webpack-4-quickstart.git

進入到資料夾內,安裝依賴:

cd webpack-4-quickstart/ && npm i

用你最喜歡的編輯器開啟工程目錄,然後清空App.js裡的內容。

接下來讓我們享受Promise吧。

假設你想通過fetch從API層獲取資料。

這是很標準的的React流程。

你把API呼叫放到元件的componentDidMount方法裡,程式碼如下:

// FILE: App.js
import React, { Component } from "react";
import ReactDOM from "react-dom";

class App extends Component {
  constructor() {
    super();
    this.state = { data: [] };
  }

  componentDidMount() {
    fetch(`https://api.coinmarketcap.com/v1/ticker/?limit=10`)
      .then(res => res.json())
      .then(json => this.setState({ data: json }));
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.data.map(el => (
            <li>
              {el.name}: {el.price_usd}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default App;

ReactDOM.render(<App />, document.getElementById("app"));
複製程式碼

(上面這個元件只是一個示例:沒有進行異常捕獲處理,讓我們假設,我們的fetch呼叫一帆風順,沒有出現任何錯誤。)

如果你通過webpack-dev-server執行需要執行:

npm start

你會看到程式碼效果如期而至(雖然很醜,但是起碼執行正常):

pic2

毫無想象力可言對吧?

我們可以做的更好嗎?在componentDidMount方法上使用async/await真的會更好嗎?

讓我們試試看!

使用async/await語法規則

從7.6.0版本開始Node就廣泛支援async/await語法了。

在前端則是另一番景象。async/await並不能全面覆蓋所有的瀏覽器。

pic3

(我知道,誰他孃的在乎IE?)

總而言之,在React中使用async/await 一點也不神奇。

我們應該在React的元件的哪裡使用async/await呢?

像獲取網路資料或者初始化事務放在React的componentDidMount方法裡一樣,把async/await放到這裡是一個不錯選擇。

以下是幾個在React裡使用async/await的步驟:

  1. 把async關鍵字放到你的函式前
  2. 在函式體內使用await關鍵字
  3. catch捕獲異常

還有一件事就是:async/await 並不是支援所有的瀏覽器,這個細節你必須注意。

create-react-app支援開箱即用的async/await。

但是如果你想在自己的配置的webpack模板工程內使用,你會遇到一個錯誤(我們馬上就會提到)。

現在讓我們在React元件內運用async/await吧。

開啟App.js檔案然後修改componentDidMount函式:

// FILE: App.js
import React, { Component } from "react";
import ReactDOM from "react-dom";

class App extends Component {
  constructor() {
    super();
    this.state = { data: [] };
  }

  async componentDidMount() {
    const response = await fetch(`https://api.coinmarketcap.com/v1/ticker/?limit=10`);
    const json = await response.json();
    this.setState({ data: json });
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.data.map(el => (
            <li>
              {el.name}: {el.price_usd}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default App;

ReactDOM.render(<App />, document.getElementById("app"));
複製程式碼

沒有捕獲到異常,再一次的讓我們假設我們的fetch呼叫平安無事。

在瀏覽器的console介面內瞅一眼。

工作正常嗎?

pic4

regeneratorRuntime is not defined? 這是啥?

為了使用async/await,你該如何解決這個報錯?

簡單!

解決這個報錯的關鍵就是babel-preset-env

在我的webpack 快速入門專案裡包含著這個配置,如果你用的是自己已有的webpack配置模板,請確保你做了如下安裝:

npm i babel-preset-env --save-dev

開啟.babelrc檔案,做如下內容更新:

{
    "presets": [
      ["env", {
        "targets": {
          "browsers": [
            ">0.25%",
            "not ie 11",
            "not op_mini all"
          ]
        }
      }], "react"
    ]
}
複製程式碼

可以檢視Jamie Kyle’last 2 wersions harmful學習更多。

儲存檔案然後瞅一眼瀏覽器。

搞定了!

pic5

整潔而優雅,但是我們革命尚未完成!

異常該怎麼處理呢?

如果使用者們掉線了或者API當機了怎麼辦?

下個章節我們就會討論如何使用fetch和async/await處理異常

處理異常

我們看過很多不進行異常處理的示例:

async componentDidMount() {
  const response = await fetch(`https://api.coinmarketcap.com/v1/ticker/?limit=10`);
  const json = await response.json();
  this.setState({ data: json });
}
複製程式碼

我承認,在真正的app內,你會把fetch請求從檢視層解耦出來。

然而,fetch APIs 在處理異常方面有一些說明。

TJ Van Toll有一篇不錯的的文章對此作說明介紹:Handling Failed HTTP Responses With fetch()

所以,我們該如何讓我們的程式碼更可靠呢?

讓我們在元件裡做個實驗吧。

通過移除URL裡的coinmarketcap引入一個異常:

async componentDidMount() {
  const response = await fetch(`https://api.com/v1/ticker/?limit=10`);
  const json = await response.json();
  this.setState({ data: json });
}
複製程式碼

執行程式碼,瞅一眼console,你會發現:

  • TypeError: NetworkError when attempting to fetch resource, (火狐瀏覽器)
  • Uncaught (in promise) TypeError: Failed to fetch, (谷歌瀏覽器)

這裡有一個沒能捕獲的異常。

讓我們抓住它。

新增一個try/catch塊:

async componentDidMount() {
  try {
    const response = await fetch(`https://api.com/v1/ticker/?limit=10`);
    const json = await response.json();
    this.setState({ data: json });
  } catch (error) {
    console.log(error);
  }
}
複製程式碼

然後再次執行程式碼。

你會看到日誌列印如下。

首先就是:wrap fetch inside a try/catch block to handle network errors.

現在讓我們在嘗試一下其他方式。

如果度過TJ Van Toll的那篇文章,對於下面這個示例你就不會感到震驚。

請看程式碼:

async componentDidMount() {
  try {
    const response = await fetch(`http://httpstat.us/500`);
  } catch (error) {
    console.log(error);
  }
}
複製程式碼

你在日誌皮膚看到什麼了嗎?毛都沒有,沒有異常,如同寸草不生的不毛之地。

為啥?

壞訊息就是:當出現網路異常情況的時候,Fetch只會返回一個Promise物件。 譬如:使用者掉線,DNS域名解析異常。

對於404 或者500這樣的返回,你不會看到任何異常。

也就意味著你必須自己檢查處理response返回體

好訊息就是:Fetch的正常返回體都攜帶一個屬性欄位(叫做“ok”)是布林型別的,true或false取決於HTTP的返回結果。

在下面的案例中,你可以用下面的方式處理異常:

async componentDidMount() {
  try {
    const response = await fetch(`http://httpstat.us/500`);
    if (!response.ok) {
      throw Error(response.statusText);
    }
  } catch (error) {
    console.log(error);
  }
}
複製程式碼

如我們設想的那樣,沒有任何異常資訊列印。

這時候,你可以向使用者展示一些錯誤資訊或者其他的有意義的內容。

所以第二點就是:對於HTTP的異常情況,Fetch並不會返回一個Promise物件。 自行檢查返回體的ok欄位。

回到我們的示例,完整程式碼如下:

async componentDidMount() {
  try {
    const response = await fetch(`https://api.coinmarketcap.com/v1/ticker/?limit=10`);
    if (!response.ok) {
      throw Error(response.statusText);
    }
    const json = await response.json();
    this.setState({ data: json });
  } catch (error) {
    console.log(error);
  }
}
複製程式碼

這個版本的處理異常的方式提供一個可靠的起始點。

再次重申,在真正的開發場景中你勢必會吧fetch請求從檢視層解耦出來,但那又是另外一回事了。

想要學習更多關於Promise rejection in Async Functions請點選How to Throw Errors From Async Functions in Javascript

封裝

從7.6.0版本往後,Node就開始全面支援async/await語法了。

async/await 能讓你的程式碼更整潔和可讀。

語法很方便,你會想要在你的React元件內使用的。

create-react-app 支援開箱即用的async/await.

但是如果你是用自己配製的webpack模板,你會遇到regeneratorRuntime is not defined異常。

解決這個異常的的關鍵是?babel-preset-env 和一些簡單的配置。

從此你就只可以在React世界中放飛async/await了。

用在哪呢?

用的最多的地方就是用來獲取網路資料和初始化一些資料的生命週期函式componentDidMount裡,這裡是一個絕佳的位置使用async/await.

遵循以下步驟,你便可以在React中使用async/await:

  • 配置babel,指定目標瀏覽器
  • 在componentDidMount方法錢使用async關鍵字
  • 在componentDidMount函式體內使用await關鍵字
  • 確定你有做異常處理

如果你在自己的程式碼裡使用 Fetch API ,在處理異常的時候要當心一些警告。

然後你就準備好了!

FAQ

componentDidMount是使用async/await最為合理的地方。

畢竟當元件瓜子啊完畢之後你想盡快的獲取資料。

我的一位學生指出,你不能在componentWillMount方法內使用async/await

那是正確的:你不能在componentWillMount方法內使用Promise。

不管怎麼說,我會儘量少去componentWillMount這個方法內做一些操作的:那是一個在React生命週期中逐漸消逝得到方法

其他一些常見問題就是當在React中使用了async/await之後,包體的大小。

在不遠的過去,如果不使用babel polyfill,你是無法在瀏覽器端使用async/await的。

使用它的結果就是,包體體積龐大。

如今有了babel-preset-env和目標瀏覽器的配置,情況不可同日而語。

包體大小仍在可接受的範圍之內。

謝閱!

在老司機開車之前,抓緊時間上車!

相關文章