非同步函式:讓承諾易於使用

非同步函式可讓您編寫以承諾為基準的程式碼,就像同步作業一樣。

Jake Archibald
Jake Archibald

根據預設,Chrome、Edge、Firefox 和 Safari 都會啟用非同步功能,以及 他們真是太不可思議了可讓你編寫承諾使用的程式碼 但是沒有封鎖主執行緒的話。會把 較簡單的非同步程式碼更容易閱讀

非同步函式的運作方式如下:

async function myFirstAsyncFunction() {
  try {
    const fulfilledValue = await promise;
  } catch (rejectedValue) {
    // …
  }
}

如果在函式定義之前使用 async 關鍵字,接著可以使用 函式中的 await。當您 await 承諾時,函式就會暫停 並以無阻塞的方式,直到達成承諾如果保證符合 才能取得價值如果承諾產品遭拒,系統就會擲回遭拒的值。

瀏覽器支援

瀏覽器支援

  • Chrome:55。
  • Edge:15,
  • Firefox:52。
  • Safari:10.1.

資料來源

範例:記錄擷取作業

假設您想擷取網址並將回應記錄為文字。以下是顯示畫面 承諾使用以下承諾:

function logFetch(url) {
  return fetch(url)
    .then((response) => response.text())
    .then((text) => {
      console.log(text);
    })
    .catch((err) => {
      console.error('fetch failed', err);
    });
}

以下是使用非同步函式的做法:

async function logFetch(url) {
  try {
    const response = await fetch(url);
    console.log(await response.text());
  } catch (err) {
    console.log('fetch failed', err);
  }
}

行數相同,但所有回呼都消失。如此一來 更容易閱讀,對於不熟悉承諾的人而言更是如此。

非同步傳回值

無論您是否使用 await,非同步函式都「一律」傳回承諾值。沒錯 保證會解析非同步函式傳回的內容,或拒絕含有 任何非同步函式擲回的內容因此,您可以使用:

// wait ms milliseconds
function wait(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function hello() {
  await wait(500);
  return 'world';
}

...呼叫 hello() 會傳回承諾,具有 "world" 的「執行要求」

async function foo() {
  await wait(500);
  throw Error('bar');
}

...呼叫 foo() 會傳回使用 Error('bar') 拒絕的承諾。

範例:串流回應

在更複雜的範例中,非同步函式的優點會增加。假設您想要 在登出區塊時串流回應,然後傳回最終大小。

以下為承諾:

function getResponseSize(url) {
  return fetch(url).then((response) => {
    const reader = response.body.getReader();
    let total = 0;

    return reader.read().then(function processResult(result) {
      if (result.done) return total;

      const value = result.value;
      total += value.length;
      console.log('Received chunk', value);

      return reader.read().then(processResult);
    });
  });
}

看看我,Jake「Welder of Promiss」「封存」。查看通話方式 是否內部 processResult() 來設定非同步迴圈?寫作創作 我覺得非常聰明。但就像最「聰明」的您必須全心投入 但孩子們會想一探究竟 90 年代

請使用非同步函式再試一次:

async function getResponseSize(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  let result = await reader.read();
  let total = 0;

  while (!result.done) {
    const value = result.value;
    total += value.length;
    console.log('Received chunk', value);
    // get the next result
    result = await reader.read();
  }

  return total;
}

所有「聰明」消失。這個非同步迴圈讓我覺得很自在 並替換成可靠、無趣的表情。這樣好多了。在未來 非同步疊代器使用 For-of-of 迴圈取代 while 迴圈,使其更臻完善。

其他非同步函式語法

我已經顯示async function() {}了,但async關鍵字可以 與其他函式語法搭配使用:

箭頭函式

// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
  const response = await fetch(url);
  return response.json();
});
敬上

物件方法

const storage = {
  async getAvatar(name) {
    const cache = await caches.open('avatars');
    return cache.match(`/avatars/${name}.jpg`);
  }
};

storage.getAvatar('jaffathecake').then();

類別方法

class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jaffathecake').then();
敬上

請留意!避免過於循序漸進

雖然您編寫的程式碼為同步性質,但請確保您不會遺漏 能夠同時執行各種操作

async function series() {
  await wait(500); // Wait 500ms…
  await wait(500); // …then wait another 500ms.
  return 'done!';
}

上述作業需要 1000 毫秒才能完成,但:

async function parallel() {
  const wait1 = wait(500); // Start a 500ms timer asynchronously…
  const wait2 = wait(500); // …meaning this timer happens in parallel.
  await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
  return 'done!';
}

上述需要 500 毫秒才能完成,因為這兩個等待事件會同時發生。 我們來看看一個實際範例。

範例:輸出擷取順序

假設您想擷取一系列網址並盡快記錄, 正確順序。

深呼吸 - 外觀如下:

function markHandled(promise) {
  promise.catch(() => {});
  return promise;
}

function logInOrder(urls) {
  // fetch all the URLs
  const textPromises = urls.map((url) => {
    return markHandled(fetch(url).then((response) => response.text()));
  });

  // log them in order
  return textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise).then((text) => console.log(text));
  }, Promise.resolve());
}

沒錯,我使用 reduce 鏈結一系列承諾。我所以 智慧型功能。不過,這還有一點聰明的編碼,沒關係。

但是,在將上述函式轉換為非同步函式時,您可能會很想 依序

不建議使用 - 順序太長
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
敬上 看來很緊張,但是我的第二次擷取要等到第一次擷取 依此類推這比承諾的例子還慢 會同時執行擷取作業幸好有個理想的中場。
建議採用 - 最好同時平行處理
function markHandled(...promises) {
  Promise.allSettled(promises);
}

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });

  markHandled(...textPromises);

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
敬上 在此範例中,系統同時擷取和讀取網址,但會同時擷取「智慧型」網址 reduce 位元已替換為標準無趣且可讀取的迴圈。
,瞭解如何調查及移除這項存取權。

瀏覽器支援的替代方案:產生器

如果您指定的瀏覽器支援產生器 (包括 其實是最新版 ),您可以排序 polyfill async 函式。

Babel 會為您代勞, 請觀看 Babel REPL 的範例

,瞭解如何調查及移除這項存取權。

我建議你把作業改成 目標瀏覽器支援非同步函式,但如果您「確實」不想使用 可將程式碼轉譯為解碼器 Babel 的 polyfill 並親自使用而不是這樣

async function slowEcho(val) {
  await wait(1000);
  return val;
}

您要加入 polyfill 然後寫入:

const slowEcho = createAsyncFunction(function* (val) {
  yield wait(1000);
  return val;
});

請注意,您必須將產生器 (function*) 傳遞至 createAsyncFunction, 並使用 yield 取代 await。除此之外,使用方法也大同小異。

解決方法:重新產生器

如果您指定舊版瀏覽器,Babel 也可以採用轉譯器的轉譯器 可讓您從 IE8 至今使用非同步函式。如要這麼做, Babel 的 es2017 預設 「和」es2015 預設設定

輸出內容不如美化,因此請留意 大量程式碼

同步處理所有內容!

所有瀏覽器都導入非同步函式後, 承諾傳回函式!不但讓程式碼更簡潔 請確保函式「一律」傳回承諾值。

我很期待收到非同步函式 2014 年, 能用瀏覽器看待他們真的會很開心太好了!