JavaScript異步編程大冒險: Async/Await

Async/Await 是什麼?

Async/Await 也就是你們知道的異步函數,它是一個用來控制 JavaScript 異步流程的一個記號。而在不少現代瀏覽器上也曾實現過這樣的設想。它的靈感來源於C# 和 F#,如今 Async/Await 在ES2017已經平穩着陸。javascript

一般咱們認爲 async function 是一個能返回 Promisefunction 。你也能夠在 async function 使用 await 關鍵字。 await 關鍵字能夠放在一個須要返回Promise的表達式前,所獲得的值被從Promise裏面剝離開,以便能用更直觀的同步體驗。咱們來看一下實際的代碼更直觀。java

// 這是一個簡單的返回 Promise 函數
// 功能是在兩秒之後 resolve("MESSAGE") .
function getMessage() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("MESSAGE"), 2000);
  });
}
複製代碼
async function start() {
  const message = await getMessage();
  return `The message is: ${message}`;
}
複製代碼
start().then(msg => console.log(msg));
// "The message is: MESSAGE"
複製代碼

爲何要用 Async/Await?

Async/Await 提供了一個看起來相對同步的方法來執行異步代碼。同時也提供了一種簡潔而直觀的方法來處理異步的錯誤,由於它實現了try…catch 標記,這是JavaScript裏面最多見的一種同步模式。node

在咱們開始冒險以前,咱們應該清楚,Async/Await 是創建在 JavaScript Promises 上的,並且關於它的知識是很重要的。json

關於記號

Async 函數

要建立一個 async 函數,通常就要把 async 關鍵字放在聲明函數以前,就像這樣:api

async function fetchWrapper() {
  return fetch('/api/url/');
}

const fetchWrapper = async () => fetch('/api/url/');
複製代碼
const obj = {
  async fetchWrapper() {
    // ...
  }
}
複製代碼

Await 關鍵字

async function updateBlogPost(postId, modifiedPost) {
  const oldPost = await getPost(postId);
  const updatedPost = { ...oldPost, ...modifiedPost };
  const savedPost = await savePost(updatedPost);
  return savedPost;
}
複製代碼

在這裏的 await 是用在其餘返回 promise 的函數前。在第一行,oldPost被賦值爲getPost執行resolve後返回的value。在下一行,咱們使用瞭解構賦值來演示怎樣把 oldPost 和 modifiedPost 合併。最終咱們把 post 儲存下來,返回了 savedPost 的結果。promise

示例 / FAQ

🖐️「到底怎麼處理錯誤?」瀏覽器

這是一個好問題!當你使用 async/await 的時候,你也能夠使用 try...catch 。在下面展現了,咱們異步的 fetch 了一些東西,返回了某種錯誤,咱們能夠在catch裏面拿到錯誤。bash

async function tryToFetch() {
  try {
    const response = await fetch('/api/data', options);
    return response.json();
  } catch(err) {
    console.log(`An error occured: ${err}`);
    // 比起返回一個錯誤
    // 咱們能夠返回一個空的data
    return { data: [] };
  }
}
複製代碼
tryToFetch().then(data => console.log(data));
複製代碼

🖐️ ️「我仍是不知道爲何 async/await 比 callbacks/promises 好.」併發

很高興你問了這個問題,這裏有一個例子能夠說明不一樣。咱們這裏只是想要異步的 fetch 一些數據,而後獲得數據後,簡單的返回一些通過處理的data,若是有錯誤,咱們簡單的只是想要返回一個對象。app

// 咱們這裏有 fetchSomeDataCB, 和 processSomeDataCB
// NOTE: CB 表明 callback

function doWork(callback) {
  fetchSomeDataCB((err, fetchedData) => {
    if(err) {
      callback(null, [])
    }

    processSomeDataCB(fetchedData, (err2, processedData) => {
      if(err2) {
        callback(null, []);
      }

      // return the processedData outside of doWork
      callback(null, processedData);
    });
  });
}

doWork((err, processedData) => console.log(processedData));
複製代碼
// 咱們這裏有 fetchSomeDataP, 和 processSomeDataP
// NOTE: P 意味着這個函數返回一個 Promise

function doWorkP() {
  return fetchSomeDataP()
    .then(fetchedData => processSomeDataP(fetchedData))
    .catch(err => []);
}

doWorkP().then(processedData => console.log(processedData));
複製代碼
async function doWork() {
  try {
    const fetchedData = await fetchSomeDataP();
    return processSomeDataP(fetchedData);
  } catch(err) {
    return [];
  }
}

doWork().then(processedData => console.log(processedData));
複製代碼

Callback vs Promise vs Async/Await

🖐️「他的併發性如何」

當咱們須要有順序的作一些事情,咱們一般用await一個一個聲明全部的步驟。在這以前爲了理解併發,咱們必須使用Promise.all。若是咱們如今有三個異步動做須要平行執行,在加上await以前,咱們須要讓全部的Promise先開始。

// 這不是解決方法,他們會逐個執行
async function sequential() {
  const output1 = await task1();
  const output2 = await task2();
  const output3 = await task3();
  return combineEverything(output1, output2, output3);
}
複製代碼

由於上述代碼只是依次的執行了三個任務,而沒有併發的執行,後一個會依賴前一個執行完成。因此咱們要改形成Promise.all 的方式。

// 這就能夠併發的執行
async function parallel() {
  const promises = [
    task1(),
    task2(),
    task3(),
  ];
  const [output1, output2, output 3] = await Promise.all(promises);
);
  return combineEverything(output1, output2, output3);
}
複製代碼

在這個例子上,咱們首先執行了3個異步的任務,以後把Promise都儲存進了一個array。咱們使用 Promise.all 來完成來所有併發結果的收集。

另外的一些提示

  • 你很容易會忘記每次你 await 一些代碼, 你須要聲明這個函數是一個 async function
  • 當你使用 await 時候,它值暫停了所涉及的 async function 。 換句話說,下面的代碼會在其餘東西log以前log 'wanna race?'
const timeoutP = async (s) => new Promise((resolve, reject) => {
  setTimeout(() => resolve(s*1000), s*1000)
});
複製代碼
[1, 2, 3].forEach(async function(time) {
  const ms = await timeoutP(time);
  console.log(`This took ${ms} milliseconds`);
});
複製代碼
console.log('wanna race?');
複製代碼

當你的第一個await 的 promise在主線程上返回執行告終果,在forEach外面的log不會被阻塞。

瀏覽器支持

看一看這張瀏覽器支持表

Node 支持

node 7.6.0 以及以上版本支持 Async/Await !

做者:Benjamin Diuguid

原文:Asynchronous Adventures in JavaScript: Async/Await

翻譯:Dominic Ming

相關文章
相關標籤/搜索