成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Async/Await初學(xué)者指南

開發(fā) 前端
JavaScript中的async和await關(guān)鍵字提供了一種現(xiàn)代語法,幫助我們處理異步操作。在本教程中,我們將深入研究如何使用async/await來掌控JavaScript程序中的流程控制。

在JavaScript中,一些操作是異步的。這意味著它們產(chǎn)生的結(jié)果或者值不會(huì)立即奏效。

看看下面的代碼:

function fetchDataFromApi() {
  // Data fetching logic here
  console.log(data);
}

fetchDataFromApi();
console.log('Finished fetching data');

JavaScript解釋器不會(huì)等待異步fetchDataFromApi函數(shù)完成后再解釋下一條語句。因此,在打印API返回的真實(shí)數(shù)據(jù)之前,它就會(huì)打印Finished fetching data。

大多數(shù)情況下,這并不是我們想要的行為。幸運(yùn)的是,我們可以使用async和await關(guān)鍵字,使我們的程序在繼續(xù)前進(jìn)之前等待異步操作的完成。

這個(gè)功能是在ES2017引入JavaScript的,在所有現(xiàn)代瀏覽器[1]中都支持。

如何創(chuàng)建JavaScript異步函數(shù)

讓我們近距離看看fetchDataFromApi數(shù)據(jù)獲取的邏輯。在JavaScript中,數(shù)據(jù)獲取是典型的異步操作案例。

使用Fetch API,我們可以這么做:

function fetchDataFromApi() {
  fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
    .then(res => res.json())
    .then(json => console.log(json.joke));
}

fetchDataFromApi();
console.log('Finished fetching data');

這里,我們從JokeAPI[2]獲取一個(gè)編程笑話。API的響應(yīng)是JSON格式的,所以我們在請求完成后提取該響應(yīng)(使用json()方法),然后把這個(gè)笑話打印到控制臺(tái)。

請注意,JokeAPI是第三方API,我們不能保證返回笑話的質(zhì)量。

如果在瀏覽器中運(yùn)行該代碼,或者在Node中(17.5+版本中使用--experimental-fetch)運(yùn)行,我們將看到,事情仍然以錯(cuò)誤的順序打印在控制臺(tái)中。

讓我們來改變它。

async關(guān)鍵字

我們需要做的第一件事是將包含的函數(shù)標(biāo)記為異步的。我們可以通過使用async關(guān)鍵字來做到這一點(diǎn),我們把它放在function關(guān)鍵字的前面:

async function fetchDataFromApi() {
  fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
    .then(res => res.json())
    .then(json => console.log(json.joke));
}

異步函數(shù)總是返回一個(gè)promise(后面會(huì)詳細(xì)介紹),所以可以通過在函數(shù)調(diào)用上鏈接一個(gè)then()來獲得正確的執(zhí)行順序:

fetchDataFromApi()
  .then(() => {
    console.log('Finished fetching data');
  });

如果現(xiàn)在運(yùn)行代碼,看到的結(jié)果會(huì)是這樣的:

If Bill Gates had a dime for every time Windows crashed ... Oh wait, he does.
Finished fetching data

但我們并不想這樣做!JavaScript的promise語法可能會(huì)有點(diǎn)毛糙,而這正是async/await的優(yōu)勢所在:它使我們能夠用一種看起來更像同步代碼的語法來編寫異步代碼,而且更容易閱讀。

await關(guān)鍵字

接下來要做的是,在我們的函數(shù)中的任何異步操作前面加上 await 關(guān)鍵字。這將迫使JavaScript解釋器"暫停"執(zhí)行并等待結(jié)果。我們可以將這些操作的結(jié)果分配給變量:

async function fetchDataFromApi() {
  const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
  const json = await res.json();
  console.log(json.joke);
}

我們還需要等待調(diào)用fetchDataFromApi函數(shù)的結(jié)果:

await fetchDataFromApi();
console.log('Finished fetching data');

很不幸,如果嘗試運(yùn)行代碼,會(huì)得到一個(gè)錯(cuò)誤:

Uncaught SyntaxError: await is only valid in async functions, async generators and modules

這是因?yàn)槲覀儾荒茉诜悄K腳本中的async函數(shù)之外使用await。我們將在后面詳細(xì)討論這個(gè)問題,但現(xiàn)在解決這個(gè)問題的最簡單的方法是將調(diào)用的代碼包裹在一個(gè)自己的函數(shù)中,我們也會(huì)將其標(biāo)記為async:

async function fetchDataFromApi() {
  const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
  const json = await res.json();
  console.log(json.joke);
}

async function init() {
  await fetchDataFromApi();
  console.log('Finished fetching data');
}

init();

如果現(xiàn)在運(yùn)行代碼,一切都如愿:

UDP is better in the COVID era since it avoids unnecessary handshakes.
Finished fetching data

我們需要這個(gè)額外的模板是不幸的,但在我看來,這個(gè)代碼仍然比基于promise的版本更容易閱讀。

聲明異步函數(shù)的不同方式

先前的例子中,使用了兩個(gè)具名函數(shù)聲明(function關(guān)鍵字后跟著函數(shù)名字),但我們并不局限于這些。我們也可以把函數(shù)表達(dá)式、箭頭函數(shù)和匿名函數(shù)標(biāo)記為async。

「異步函數(shù)表達(dá)式」

當(dāng)我們創(chuàng)建一個(gè)函數(shù),并將其賦值給一個(gè)變量時(shí),這便是「函數(shù)表達(dá)式」。該函數(shù)是匿名的,這意味著它沒有名字。比如:

const fetchDataFromApi = async function() {
  const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
  const json = await res.json();
  console.log(json.joke);
}

這將以與我們之前的代碼完全相同的方式工作。

「異步箭頭函數(shù)」

箭頭函數(shù)在ES6被引入。它們是函數(shù)表達(dá)式的緊湊替代品,并且總是匿名的。它們的基本語法如下:

(params) => { <function body> }

為了標(biāo)記箭頭函數(shù)為匿名的,在左括號(hào)前插入async關(guān)鍵字。

舉個(gè)例子,除了在上面的代碼中創(chuàng)建一個(gè)額外的init函數(shù)外,另一個(gè)辦法是將現(xiàn)有的代碼包裹在一個(gè)IIFE中,我們將其標(biāo)記為async:

(async () => {
  async function fetchDataFromApi() {
    const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
    const json = await res.json();
    console.log(json.joke);
  }
  await fetchDataFromApi();
  console.log('Finished fetching data');
})();

使用函數(shù)表達(dá)式或函數(shù)聲明并沒有什么大的區(qū)別:大部分情況下,這只是一個(gè)使用偏好的問題。但有幾件事情需要注意,比如變量提升,或者箭頭函數(shù)無法綁定this的事實(shí)。

Await/Async內(nèi)部機(jī)制

正如你可能已經(jīng)猜到的,async/await在很大程度上是promise的語法糖。讓我們更詳細(xì)地看一下這個(gè)問題,因?yàn)楦玫乩斫鈨?nèi)部發(fā)生的事情將對理解async/await的工作方式有很大幫助。

第一件需要注意的事情是,async函數(shù)總是返回一個(gè)promise,即使我們不顯式地告訴它這么做。比如:

async function echo(arg) {
  return arg;
}

const res = echo(5);
console.log(res);

打印結(jié)果如下:

Promise { <state>: "fulfilled", <value>: 5 }

promise可能會(huì)是三種狀態(tài)之一:pending、fulfilled、或者rejected。一個(gè)promise開始時(shí)處于pending狀態(tài)。如果與該promise有關(guān)的行為成功了,該promise就被稱為fulfilled。如果行為不成功,該promise就被稱為rejected。一旦promise是fulfilled或者rejected,但不是pending,它也被認(rèn)為是settled。

當(dāng)我們在async函數(shù)中使用 await 關(guān)鍵字來"暫停"函數(shù)執(zhí)行時(shí),真正發(fā)生的是我們在等待一個(gè)promise(無論是顯式還是隱式)進(jìn)入resolved或rejected狀態(tài)。

基于上述示例,我們可以這么做:

async function echo(arg) {
  return arg;
}

async function getValue() {
  const res = await echo(5);
  console.log(res);
}

getValue();
// 5

因?yàn)閑cho函數(shù)返回一個(gè)promise,而getValue函數(shù)中的await關(guān)鍵字在繼續(xù)程序之前等待這個(gè)promise完成,所以我們能夠?qū)⑺璧闹荡蛴〉娇刂婆_(tái)。

promise是對JavaScript中流程控制的一大改進(jìn),并且被一些較新的瀏覽器API所使用。比如Battery status API[3]、Clipboard API[4]、Fetch API[5]、MediaDevices API[6]等等。

Node還在其內(nèi)置的util模塊中添加了一個(gè)promise函數(shù),可以將使用回調(diào)函數(shù)的代碼轉(zhuǎn)換為返回promise。而從v10開始,Node的fs模塊中的函數(shù)可以直接返回promise。

從promise到async/await的轉(zhuǎn)換

那么,為什么這一切對我們來說都很重要呢?

好消息是,任何返回promise的函數(shù)都可以使用async/await。我并不是說我們應(yīng)該對所有的事情都使用async/await(該語法確實(shí)有其缺點(diǎn),我們將在討論錯(cuò)誤處理時(shí)看到),但我們應(yīng)該意識(shí)到這是可能的。

我們已經(jīng)看到了如何改變基于promise的獲取調(diào)用,使之與async/await一起工作,所以讓我們看另一個(gè)例子。這里有一個(gè)小的實(shí)用函數(shù),使用Node基于promise的API和它的readFile方法來獲取一個(gè)文件的內(nèi)容。

使用Promise.then():

const { promises: fs } = require('fs');

const getFileContents = function(fileName) {
  return fs.readFile(fileName, enc)
}

getFileContents('myFile.md', 'utf-8')
  .then((contents) => {
    console.log(contents);
  });

有了async/await就會(huì)變成:

import { readFile } from 'node:fs/promises';

const getFileContents = function(fileName, enc) {
  return readFile(fileName, enc)
}

const contents = await getFileContents('myFile.md', 'utf-8');
console.log(contents);

注意:這是在利用一個(gè)叫做top-level await的功能,它只在ES模塊中可用。要運(yùn)行這段代碼,請將文件保存為index.mjs并使用Node>=14.8的版本。

雖然這些都是簡單的例子,但我發(fā)現(xiàn)async/await的語法更容易理解。當(dāng)處理多個(gè)then()語句和錯(cuò)誤處理時(shí),這一點(diǎn)變得尤其真實(shí)。

錯(cuò)誤處理

在處理異步函數(shù)時(shí),有幾種方法來處理錯(cuò)誤。最常見的可能是使用try...catch塊,我們可以把它包在異步操作中并捕捉任何發(fā)生的錯(cuò)誤。

在下面的例子中,請注意我是如何將URL改成不存在的東西的:

async function fetchDataFromApi() {
  try {
    const res = await fetch('https://non-existent-url.dev');
    const json = await res.json();
    console.log(json.joke);
  } catch (error) {
    // Handle the error here in whichever way you like
    console.log('Something went wrong!');
    console.warn(error)
  }
}

await fetchDataFromApi();
console.log('Finished fetching data');

這將導(dǎo)致以下信息被打印到控制臺(tái):

Something went wrong!
TypeError: fetch failed
    ...
    cause: Error: getaddrinfo ENOTFOUND non-existent-url.dev
Finished fetching data

這種結(jié)果是因?yàn)閒etch返回一個(gè)promise。當(dāng)fetch操作失敗時(shí),promise的reject方法被調(diào)用,await關(guān)鍵字將這種reject轉(zhuǎn)換為一個(gè)可捕捉的錯(cuò)誤。

然而,這種方法有幾個(gè)問題。主要的問題是它很啰嗦,而且相當(dāng)難看。想象一下,我們正在構(gòu)建一個(gè)CRUD應(yīng)用程序,我們?yōu)槊總€(gè)CRUD方法(創(chuàng)建、讀取、更新、銷毀)都有一個(gè)單獨(dú)的函數(shù)。如果這些方法中的每一個(gè)都進(jìn)行了異步API調(diào)用,我們就必須把每個(gè)調(diào)用包在自己的try...catch塊中。這是相當(dāng)多的額外代碼。

另一個(gè)問題是,如果我們不使用await關(guān)鍵字,這將導(dǎo)致一個(gè)未處理的拒絕的promise:

import { readFile } from 'node:fs/promises';

const getFileContents = function(fileName, enc) {
  try {
    return readFile(fileName, enc)
  } catch (error) {
    console.log('Something went wrong!');
    console.warn(error)
  }
}

const contents = await getFileContents('this-file-does-not-exist.md', 'utf-8');
console.log(contents);

上述代碼的打印如下:

node:internal/process/esm_loader:91
    internalBinding('errors').triggerUncaughtException(
                              ^
[Error: ENOENT: no such file or directory, open 'this-file-does-not-exist.md'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'this-file-does-not-exist.md'
}

與await不同,return關(guān)鍵字不會(huì)將拒絕的promise轉(zhuǎn)化為可捕捉的錯(cuò)誤。

在函數(shù)調(diào)用中使用catch()

每個(gè)返回promise的函數(shù)都可以利用promise的catch方法來處理任何可能發(fā)生的promise拒絕。

有了這個(gè)簡單的補(bǔ)充,上例中的代碼將優(yōu)雅地處理錯(cuò)誤:

const contents = await getFileContents('this-file-does-not-exist.md', 'utf-8')
  .catch((error) => {
    console.log('Something went wrong!');
    console.warn(error);
  });
console.log(contents);

現(xiàn)在輸出是這樣子的:

Something went wrong!
[Error: ENOENT: no such file or directory, open 'this-file-does-not-exist.md'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'this-file-does-not-exist.md'
}
undefined

至于使用哪種策略,我同意Valeri Karpov[7]的建議。使用try/catch來恢復(fù)async函數(shù)內(nèi)部的預(yù)期錯(cuò)誤,但通過在調(diào)用函數(shù)中添加catch()來處理意外錯(cuò)誤。

并行運(yùn)行異步命令

當(dāng)我們使用await關(guān)鍵字來等待一個(gè)異步操作完成時(shí),JavaScript解釋器會(huì)相應(yīng)地暫停執(zhí)行。雖然這很方便,但這可能并不總是我們想要的。考慮一下下面的代碼:

(async () => {
  async function getStarCount(repo){
    const repoData = await fetch(repo);
    const repoJson = await repoData.json()
    return repoJson.stargazers_count;
  }

  const reactStars = await getStarCount('https://api.github.com/repos/facebook/react');
  const vueStars = await getStarCount('https://api.github.com/repos/vuejs/core');
  console.log(`React has ${reactStars} stars, whereas Vue has ${vueStars} stars`)
})();

這里我們正在進(jìn)行兩次API調(diào)用,分別獲取React和Vue的GitHub star數(shù)。雖然這樣可以正常運(yùn)轉(zhuǎn),但我們沒有理由在發(fā)出第二個(gè)fetch請求之前等待第一個(gè)promise完成。如果我們要發(fā)出很多請求,這將是一個(gè)相當(dāng)大的瓶頸。

為了解決這個(gè)問題,我們可以使用Promise.all,它接收一個(gè)promise數(shù)組,并等待所有promise被解決或其中任何一個(gè)承諾被拒絕:

(async () => {
  async function getStarCount(repo){
    // As before
  }

  const reactPromise = getStarCount('https://api.github.com/repos/facebook/react');
  const vuePromise = getStarCount('https://api.github.com/repos/vuejs/core');
  const [reactStars, vueStars] = await Promise.all([reactPromise, vuePromise]);

  console.log(`React has ${reactStars} stars, whereas Vue has ${vueStars} stars`);
})();

好多了!

同步循環(huán)中的異步await

在某些時(shí)候,我們會(huì)嘗試在一個(gè)同步循環(huán)中調(diào)用一個(gè)異步函數(shù)。比如說:

// Return promise which resolves after specified no. of milliseconds
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function process(array) {
  array.forEach(async (el) => {
    await sleep(el); // we cannot await promise here
    console.log(el);
  });
}

const arr = [3000, 1000, 2000];
process(arr);

這不會(huì)像預(yù)期的那樣奏效,因?yàn)閒orEach只會(huì)調(diào)用函數(shù)而不等待它完成,以下內(nèi)容將被打印到控制臺(tái):

1000
2000
3000

同樣的事情也適用于其他許多數(shù)組方法,如map、filter和reduce。

幸運(yùn)的是,ES2018引入了異步迭代器,除了它們的next()方法會(huì)返回一個(gè)promise外,它們就像普通的迭代器。這意味著我們可以在其中使用 await。讓我們使用for...of重寫上面的代碼:

async function process(array) {
  for (el of array) {
    await sleep(el);
    console.log(el);
  };
}

現(xiàn)在,process函數(shù)的輸出就是正確的順序:

3000
1000
2000

就像我們之前等待異步fetch請求的例子一樣,這也會(huì)帶來性能上的代價(jià)。for循環(huán)中的每個(gè)await都會(huì)阻塞事件循環(huán),通常應(yīng)該重構(gòu)代碼,一次性創(chuàng)建所有的promise,然后使用Promise.all()來獲取結(jié)果。

甚至有一條ESLint規(guī)則[8],如果它檢測到這種行為就會(huì)警告。

頂層await

最后,讓我們來看看一個(gè)叫做「頂層await」的東西。這是ES2022中引入的語言,從14.8版開始在Node中可用。

當(dāng)我們在文章開頭運(yùn)行我們的代碼時(shí),我們已經(jīng)被這個(gè)東西所要解決的問題給纏住了。還記得這個(gè)錯(cuò)誤嗎?

Uncaught SyntaxError: await is only valid in async functions, async generators and modules

當(dāng)我們試圖在一個(gè)async函數(shù)之外使用await時(shí),就會(huì)發(fā)生這種情況。例如,在我們代碼的頂層:

const ms = await Promise.resolve('Hello, World!');
console.log(msg);

頂層await解決了這個(gè)問題,使上述代碼有效,但只在ES模塊中奏效。如果我們在瀏覽器中工作,我們可以把這段代碼添加到一個(gè)叫做index.js的文件中,然后像這樣把它加載到我們的頁面中:

<script src="index.js" type="module"></script>

事情會(huì)像預(yù)期的那樣工作,不需要包裝函數(shù)或丑陋的IIFE。

在Node中,事情變得更加有趣。要將一個(gè)文件聲明為ES模塊,我們應(yīng)該做兩件事中的一件。一種方法是以.mjs為擴(kuò)展名保存,然后像這樣運(yùn)行它:

node index.mjs

另一種方法是在package.json文件中設(shè)置"type": "module":

{
  "name": "myapp",
  "type": "module",
  ...
}

頂層 await 也可以和動(dòng)態(tài)導(dǎo)入很好地配合--一種類函數(shù)的表達(dá)式,它允許我們異步加載 ES 模塊。這將返回一個(gè)promise,而這個(gè)promise將被解析為一個(gè)模塊對象,這意味著我們可以這樣做:

const locale = 'DE';

const { default: greet } = await import(
  `${ locale === 'DE' ?
      './de.js' :
      './en.js'
  }`
);

greet();
// Outputs "Hello" or "Guten Tag" depending on the value of the locale variable

動(dòng)態(tài)導(dǎo)入選項(xiàng)也很適合與React和Vue等框架相結(jié)合的懶加載。這使我們能夠減少初始包的大小和交互指標(biāo)的時(shí)間。

總結(jié)

在這篇文章中,我們研究了如何使用async/await來管理你的JavaScript程序的控制流。我們討論了語法、async/await如何工作、錯(cuò)誤處理,以及一些問題。如果你已經(jīng)走到了這一步,你現(xiàn)在就是一個(gè)專家了。 ??

編寫異步代碼可能很難,特別是對初學(xué)者來說,但現(xiàn)在你已經(jīng)對這些技術(shù)有了扎實(shí)的了解,你應(yīng)該能夠運(yùn)用它們來獲得巨大的效果。

  • 本文譯自:https://www.sitepoint.com/javascript-async-await/

以上就是本文的全部內(nèi)容,如果對你有所幫助,歡迎點(diǎn)贊、收藏、轉(zhuǎn)發(fā)~

參考資料

[1]現(xiàn)代瀏覽器:https://caniuse.com/async-functions

[2]JokeAPI:https://jokeapi.dev/

[3]Battery status API:https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API

[4]Clipboard API:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API

[5]Fetch API:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

[6]MediaDevices API:https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices

[7]Valeri Karpov:https://thecodebarbarian.com/async-await-error-handling-in-javascript.html

[8]ESLint規(guī)則:https://eslint.org/docs/latest/rules/no-await-in-loop

責(zé)任編輯:武曉燕 來源: 前端F2E
相關(guān)推薦

2022-04-24 15:21:01

MarkdownHTML

2021-05-10 08:50:32

網(wǎng)絡(luò)管理網(wǎng)絡(luò)網(wǎng)絡(luò)性能

2022-03-28 09:52:42

JavaScript語言

2023-07-03 15:05:07

預(yù)測分析大數(shù)據(jù)

2022-07-22 13:14:57

TypeScript指南

2010-06-13 11:13:38

UML初學(xué)者指南

2022-10-10 15:28:45

負(fù)載均衡

2023-02-10 08:37:28

2012-03-14 10:56:23

web app

2022-09-05 15:36:39

Linux日志記錄syslogd

2018-10-28 16:14:55

Reactreact.js前端

2013-04-08 16:35:52

Adobe Edge

2011-03-02 10:57:27

vsFTPd

2013-03-06 10:40:58

Adobe Edge HTML5

2010-08-26 15:47:09

vsftpd安裝

2014-04-01 10:20:00

開源Rails

2020-08-16 13:10:46

TensorFlow深度學(xué)習(xí)數(shù)據(jù)集

2023-02-19 15:31:09

架構(gòu)軟件開發(fā)代碼

2024-04-28 10:56:34

Next.jsWeb應(yīng)用搜索引擎優(yōu)化

2021-09-08 12:29:21

物聯(lián)網(wǎng)IOT
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 黄色免费av| 日韩欧美成人一区二区三区 | 精品视频在线一区 | 中文字幕一区在线观看视频 | 欧美日韩专区 | 国产一区二区三区久久 | 国产分类视频 | 成人av在线网站 | 99在线免费观看视频 | 日韩精品一区二区在线 | 神马影院一区二区三区 | 午夜精品在线 | 一区日韩| 国产成人精品免费视频 | 粉嫩国产精品一区二区在线观看 | 中文字幕亚洲专区 | 成人国产一区二区三区精品麻豆 | 午夜精品一区二区三区在线视 | 中文字幕一区二区三区乱码图片 | av黄色国产 | 91精品国产91久久综合桃花 | 精国产品一区二区三区 | 日本三级全黄三级三级三级口周 | 性国产xxxx乳高跟 | 国产精品久久二区 | 欧美一区二区小视频 | 亚洲欧美中文日韩在线v日本 | 97avcc| 午夜久草 | 91一区二区三区 | 国内精品视频在线观看 | 日本在线播放一区二区 | 色综合久久久久 | 亚洲天堂色 | 99pao成人国产永久免费视频 | 欧美一区二区二区 | 免费在线播放黄色 | 精品国产乱码久久久久久久久 | 国产电影一区二区在线观看 | 亚洲一区二区久久 | 国内在线视频 |