Promises與 async/await

「async/await」是一種與「promise」協做的特殊的語法。它使得異步工做更加容易理解和使用。javascript

Async函數

咱們從async關鍵詞開始,它能夠被放置在任何函數的開頭位置,好比:java

async function f() {
  return 1;
}

這裏的async表示:該函數將始終返回一個promise。即便您的代碼沒有顯式返回一個promise,在JavaScript運行時也會自動包裝一個promise,用於返回指定的值。git

在這個例子中,這段代碼將會返回一個result爲1的promisegithub

async function f() {
  return 1;
}

f().then(alert); // 1

固然,咱們也能夠顯式的返回一個promise:json

async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1

async確保了函數會返回一個promise。挺簡單的對吧?接下來,是另外一個關鍵詞await,僅僅能在async標記的函數中生效。api

Await

語法說明:promise

//該段代碼僅僅能在 async 標記的函數中生效
let value = await promise;

關鍵詞await確保JavaScript運行時將會等待promise執行完畢並返回結果。
下面是一段使用promise並在一秒後返回結果的例子:app

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // 等待至promise得到結果 (*)

  alert(result); // "done!"
}

f();

該函數在運行至await時,執行了「pauses」操做,直至promise執行完畢後從新執行接下來的代碼。因此該段代碼將在一秒後顯示「done!」。
讓咱們強調一遍:await將順序使得JavaScript的運行時等待promise執行完畢,然後繼續運行餘下代碼。等待時的操做並不會消耗任何CPU資源,由於此時的運行時能夠同時執行其餘操做:執行其餘的代碼,處理事件邏輯等。
這僅僅是一項相對promise.than更爲優雅的語法來獲取promise的運行結果,更容易閱讀和編寫代碼而已。框架


不能將await用於任何標準函數異步

若是您嘗試將await運行在任何未標記爲async的函數中,都會產生一個語法錯誤

function f() {
  let promise = Promise.resolve(1);
  let result = await promise; // Syntax error
}

咱們若是沒有使用async標記函數,那麼咱們就會獲得這個語法錯誤。換句話說,await僅能夠運行在async function中。


讓咱們修改 Promises chaining 中的例子,使用async/await來重寫這個例子。

  1. 咱們須要將.then替換爲await
  2. 咱們須要將函數修改成async function
async function showAvatar() {

  // read our JSON
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // read github user
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // show the avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // wait 3 seconds
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

至關容易閱讀和理解對吧。


await 並不能在頂層環境中生效

人們在開始使用await時,老是容易忘記必須在async function內部使用。好比如下代碼將會報錯:

// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

因此咱們須要聲明一個async function來包裹該段代碼。



await 能夠接受 thenables

相似promise.thenawait准許使用then方法。須要申明的是,這裏指的是一個非promise對象,但它持有.then方法,那麼就能夠配合await使用。
好比如下例子,await接受new Thenable(1):

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { native code }
    // resolve with this.num*2 after 1000ms
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
};

async function f() {
  // waits for 1 second, then result becomes 2
  let result = await new Thenable(1);
  alert(result);
}

f();

若是await與一個含有.then方法的非promise對象組合,同時其支持resolvereject兩個方法做爲參數。那麼await將會等待其中之一的函數被調用(註釋(*)所在行)並在以後繼續運行剩餘代碼。



Async方法

類函數亦能夠被定義爲異步函數,僅需將async置於函數聲明前。
相似於:

class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}

new Waiter()
  .wait()
  .then(alert); // 1

這和以前的其餘代碼端是同樣的做用:應用await返回一個promise對象。


錯誤處理

若是promise順利完成,那麼await promise將返回一個值。但假如觸發了錯誤,那麼將在此行代碼中throw一個錯誤。

如下代碼:

async function f() {
  await Promise.reject(new Error("Whoops!"));
}

等同於:

async function f() {
  throw new Error("Whoops!");
}

在真實的運行環境中,可能須要消耗一些時間來觸發錯誤。因此await將會進行等待,而以後拋出一個錯誤。咱們能夠經過try…catch來捕獲錯誤,一樣的方法也適用於throw:

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

觸發了錯誤以後,運行代碼將跳轉至catch代碼塊。咱們可使用以下代碼:

async function f() {

  try {
    let response = await fetch('/no-user-here');
    let user = await response.json();
  } catch(err) {
    // catches errors both in fetch and response.json
    alert(err);
  }
}

f();

但若是咱們沒有使用try…catch,那麼將會在異步調用f()時觸發rejected。咱們也能夠添加.catch來處理錯誤:

async function f() {
  let response = await fetch('http://no-such-url');
}

// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)

若是咱們忘記添加.catch,咱們將得到一個未被捕獲的錯誤。咱們也可使用一個全局事件捕獲方法來處理,參見Promise chaining


async/await 和 promise.then/catch

當咱們使用async/await時,咱們僅僅須要.then,由於await會接受正確結果。咱們可使用try…catch來取代.catch。同時這也將更爲便利。
但處於頂層代碼邏輯時,咱們的邏輯代碼處在async function之外,咱們並不能直接使用await,因此添加.then/catch代碼塊來獲取最終結果是更爲廣泛的作法。



async/await與Promise.all相互合做

當咱們須要等待多個promises時,咱們可使用Promise.all以後使用await:

// wait for the array of results
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

假如觸發了一個錯誤,它也會和其餘promise同樣工做:從運行失敗的Promise.all節點中斷,以後咱們能夠經過try…catch來捕獲錯誤。


總結

async定之後的函數有兩層做用:

  1. 確保它老是返回一個promise。
  2. 容許在函數內部使用await

await關鍵詞確保js運行時將會等待promise處理完畢,而且:

  1. 若是觸發了一個運行錯誤,promise運行中斷,並在改處相似觸發throw error
  2. 此外,它將返回一個結果,因此咱們能夠將其賦值給一個變量。

咱們一塊兒提供了一個偉大的框架來更爲簡便的完成異步操做。

有了async/await的幫組,咱們能夠大幅減小使用promise.then/catch,但咱們依然不該該忘記這些技術是基於promises,極可能咱們會不得不繼續使用promise的方法。同時,Promise.all至關適合等待多個任務順序執行的操做。

相關文章
相關標籤/搜索