- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
JavaScript中的 async 和 await 关键字提供了一种现代语法,帮助我们处理异步操作。在本教程中,我们将深入研究如何使用 async/await 来掌控JavaScript程序中的流程控制.
在JavaScript中,一些操作是异步的。这意味着它们产生的结果或者值不会立即奏效.
看看下面的代码:
function fetchDataFromApi() {
// Data fetching logic here
console.log(data);
}
fetchDataFromApi();
console.log('Finished fetching data');
JavaScript解释器不会等待异步 fetchDataFromApi 函数完成后再解释下一条语句。因此,在打印API返回的真实数据之前,它就会打印 Finished fetching data .
大多数情况下,这并不是我们想要的行为。幸运的是,我们可以使用 async 和 await 关键字,使我们的程序在继续前进之前等待异步操作的完成.
这个功能是在ES2017引入JavaScript的,在所有 现代浏览器 中都支持.
让我们近距离看看 fetchDataFromApi 数据获取的逻辑。在JavaScript中,数据获取是典型的异步操作案例.
使用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 获取一个编程笑话。API的响应是JSON格式的,所以我们在请求完成后提取该响应(使用 json() 方法),然后把这个笑话打印到控制台.
请注意,JokeAPI是第三方API,我们不能保证返回笑话的质量.
如果在浏览器中运行该代码,或者在Node中(17.5+版本中使用 --experimental-fetch )运行,我们将看到,事情仍然以错误的顺序打印在控制台中.
让我们来改变它.
我们需要做的第一件事是将包含的函数标记为异步的。我们可以通过使用 async 关键字来做到这一点,我们把它放在 function 关键字的前面:
async function fetchDataFromApi() {
fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
.then(res => res.json())
.then(json => console.log(json.joke));
}
异步函数总是返回一个 promise (后面会详细介绍),所以可以通过在函数调用上链接一个 then() 来获得正确的执行顺序:
fetchDataFromApi()
.then(() => {
console.log('Finished fetching data');
});
如果现在运行代码,看到的结果会是这样的:
If Bill Gates had a dime for every time Windows crashed ... Oh wait, he does.
Finished fetching data
但我们并不想这样做!JavaScript的 promise 语法可能会有点毛糙,而这正是 async/await 的优势所在:它使我们能够用一种看起来更像同步代码的语法来编写异步代码,而且更容易阅读.
接下来要做的是,在我们的函数中的任何异步操作前面加上 await 关键字。这将迫使JavaScript解释器"暂停"执行并等待结果。我们可以将这些操作的结果分配给变量:
async function fetchDataFromApi() {
const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
const json = await res.json();
console.log(json.joke);
}
我们还需要等待调用 fetchDataFromApi 函数的结果:
await fetchDataFromApi();
console.log('Finished fetching data');
很不幸,如果尝试运行代码,会得到一个错误:
Uncaught SyntaxError: await is only valid in async functions, async generators and modules
这是因为我们不能在非模块脚本中的 async 函数之外使用 await 。我们将在后面详细讨论这个问题,但现在解决这个问题的最简单的方法是将调用的代码包裹在一个自己的函数中,我们也会将其标记为 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();
如果现在运行代码,一切都如愿:
UDP is better in the COVID era since it avoids unnecessary handshakes.
Finished fetching data
我们需要这个额外的模板是不幸的,但在我看来,这个代码仍然比基于 promise 的版本更容易阅读.
先前的例子中,使用了两个具名函数声明( function 关键字后跟着函数名字),但我们并不局限于这些。我们也可以把函数表达式、箭头函数和匿名函数标记为 async .
异步函数表达式 。
当我们创建一个函数,并将其赋值给一个变量时,这便是 函数表达式 。该函数是匿名的,这意味着它没有名字。比如:
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);
}
这将以与我们之前的代码完全相同的方式工作.
异步箭头函数 。
箭头函数在ES6被引入。它们是函数表达式的紧凑替代品,并且总是匿名的。它们的基本语法如下:
(params) => { <function body> }
为了标记箭头函数为匿名的,在左括号前插入 async 关键字.
举个例子,除了在上面的代码中创建一个额外的 init 函数外,另一个办法是将现有的代码包裹在一个IIFE中,我们将其标记为 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');
})();
使用函数表达式或函数声明并没有什么大的区别:大部分情况下,这只是一个使用偏好的问题。但有几件事情需要注意,比如变量提升,或者箭头函数无法绑定 this 的事实.
正如你可能已经猜到的, async/await 在很大程度上是 promise 的语法糖。让我们更详细地看一下这个问题,因为更好地理解内部发生的事情将对理解 async/await 的工作方式有很大帮助.
第一件需要注意的事情是, async 函数总是返回一个 promise ,即使我们不显式地告诉它这么做。比如:
async function echo(arg) {
return arg;
}
const res = echo(5);
console.log(res);
打印结果如下:
Promise { <state>: "fulfilled", <value>: 5 }
promise 可能会是三种状态之一: pending 、 fulfilled 、或者 rejected 。一个 promise 开始时处于 pending 状态。如果与该 promise 有关的行为成功了,该 promise 就被称为 fulfilled 。如果行为不成功,该 promise 就被称为 rejected 。一旦 promise 是 fulfilled 或者 rejected ,但不是 pending ,它也被认为是 settled .
当我们在 async 函数中使用 await 关键字来"暂停"函数执行时,真正发生的是我们在等待一个 promise (无论是显式还是隐式)进入 resolved 或 rejected 状态.
基于上述示例,我们可以这么做:
async function echo(arg) {
return arg;
}
async function getValue() {
const res = await echo(5);
console.log(res);
}
getValue();
// 5
因为 echo 函数返回一个 promise ,而 getValue 函数中的 await 关键字在继续程序之前等待这个 promise 完成,所以我们能够将所需的值打印到控制台.
promise 是对JavaScript中流程控制的一大改进,并且被一些较新的浏览器API所使用。比如 Battery status API 、 Clipboard API 、 Fetch API 、 MediaDevices API 等等.
Node还在其内置的 util 模块中添加了一个 promise 函数,可以将使用回调函数的代码转换为返回 promise 。而从v10开始,Node的 fs 模块中的函数可以直接返回 promise .
那么,为什么这一切对我们来说都很重要呢?
好消息是,任何返回 promise 的函数都可以使用 async/await 。我并不是说我们应该对所有的事情都使用 async/await (该语法确实有其缺点,我们将在讨论错误处理时看到),但我们应该意识到这是可能的.
我们已经看到了如何改变基于 promise 的获取调用,使之与 async/await 一起工作,所以让我们看另一个例子。这里有一个小的实用函数,使用Node基于 promise 的API和它的 readFile 方法来获取一个文件的内容.
使用 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 就会变成:
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);
注意:这是在利用一个叫做 top-level await 的功能,它只在ES模块中可用。要运行这段代码,请将文件保存为 index.mjs 并使用Node>=14.8的版本.
虽然这些都是简单的例子,但我发现 async/await 的语法更容易理解。当处理多个 then() 语句和错误处理时,这一点变得尤其真实.
在处理异步函数时,有几种方法来处理错误。最常见的可能是使用 try...catch 块,我们可以把它包在异步操作中并捕捉任何发生的错误.
在下面的例子中,请注意我是如何将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');
这将导致以下信息被打印到控制台:
Something went wrong!
TypeError: fetch failed
...
cause: Error: getaddrinfo ENOTFOUND non-existent-url.dev
Finished fetching data
这种结果是因为 fetch 返回一个 promise 。当 fetch 操作失败时, promise 的 reject 方法被调用, await 关键字将这种 reject 转换为一个可捕捉的错误.
然而,这种方法有几个问题。主要的问题是它很啰嗦,而且相当难看。想象一下,我们正在构建一个CRUD应用程序,我们为每个CRUD方法(创建、读取、更新、销毁)都有一个单独的函数。如果这些方法中的每一个都进行了异步API调用,我们就必须把每个调用包在自己的 try...catch 块中。这是相当多的额外代码.
另一个问题是,如果我们不使用 await 关键字,这将导致一个未处理的拒绝的 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 关键字不会将拒绝的 promise 转化为可捕捉的错误.
每个返回 promise 的函数都可以利用 promise 的 catch 方法来处理任何可能发生的 promise 拒绝.
有了这个简单的补充,上例中的代码将优雅地处理错误:
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);
现在输出是这样子的:
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 的建议。使用 try/catch 来恢复 async 函数内部的预期错误,但通过在调用函数中添加 catch() 来处理意外错误.
当我们使用 await 关键字来等待一个异步操作完成时,JavaScript解释器会相应地暂停执行。虽然这很方便,但这可能并不总是我们想要的。考虑一下下面的代码:
(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`)
})();
这里我们正在进行两次API调用,分别获取React和Vue的GitHub star 数。虽然这样可以正常运转,但我们没有理由在发出第二个 fetch 请求之前等待第一个 promise 完成。如果我们要发出很多请求,这将是一个相当大的瓶颈.
为了解决这个问题,我们可以使用 Promise.all ,它接收一个 promise 数组,并等待所有 promise 被解决或其中任何一个承诺被拒绝:
(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`);
})();
好多了! 。
在某些时候,我们会尝试在一个同步循环中调用一个异步函数。比如说:
// 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);
这不会像预期的那样奏效,因为 forEach 只会调用函数而不等待它完成,以下内容将被打印到控制台:
1000
2000
3000
同样的事情也适用于其他许多数组方法,如 map 、 filter 和 reduce .
幸运的是,ES2018引入了异步迭代器,除了它们的 next() 方法会返回一个 promise 外,它们就像普通的迭代器。这意味着我们可以在其中使用 await 。让我们使用 for...of 重写上面的代码:
async function process(array) {
for (el of array) {
await sleep(el);
console.log(el);
};
}
现在,process函数的输出就是正确的顺序:
3000
1000
2000
就像我们之前等待异步 fetch 请求的例子一样,这也会带来性能上的代价。 for 循环中的每个 await 都会阻塞事件循环,通常应该重构代码,一次性创建所有的 promise ,然后使用 Promise.all() 来获取结果.
甚至有一条 ESLint规则 ,如果它检测到这种行为就会警告.
最后,让我们来看看一个叫做 顶层await 的东西。这是ES2022中引入的语言,从14.8版开始在Node中可用.
当我们在文章开头运行我们的代码时,我们已经被这个东西所要解决的问题给缠住了。还记得这个错误吗?
Uncaught SyntaxError: await is only valid in async functions, async generators and modules
当我们试图在一个 async 函数之外使用 await 时,就会发生这种情况。例如,在我们代码的顶层:
const ms = await Promise.resolve('Hello, World!');
console.log(msg);
顶层 await 解决了这个问题,使上述代码有效,但只在ES模块中奏效。如果我们在浏览器中工作,我们可以把这段代码添加到一个叫做 index.js 的文件中,然后像这样把它加载到我们的页面中:
<script src="index.js" type="module"></script>
事情会像预期的那样工作,不需要包装函数或丑陋的IIFE.
在Node中,事情变得更加有趣。要将一个文件声明为ES模块,我们应该做两件事中的一件。一种方法是以 .mjs 为扩展名保存,然后像这样运行它:
node index.mjs
另一种方法是在 package.json 文件中设置 "type": "module" :
{
"name": "myapp",
"type": "module",
...
}
顶层 await 也可以和动态导入很好地配合--一种类函数的表达式,它允许我们异步加载 ES 模块。这将返回一个 promise ,而这个 promise 将被解析为一个模块对象,这意味着我们可以这样做:
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
动态导入选项也很适合与React和Vue等框架相结合的懒加载。这使我们能够减少初始包的大小和交互指标的时间.
在这篇文章中,我们研究了如何使用 async/await 来管理你的JavaScript程序的控制流。我们讨论了语法、 async/await 如何工作、错误处理,以及一些问题。如果你已经走到了这一步,你现在就是一个专家了。 🙂 。
编写异步代码可能很难,特别是对初学者来说,但现在你已经对这些技术有了扎实的了解,你应该能够运用它们来获得巨大的效果.
以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~ 。
最后此篇关于async/await初学者指南的文章就讲到这里了,如果你想了解更多关于async/await初学者指南的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个 forEach循环处理数组中的数据。在该循环中间的某个地方,我必须等待 DOM 元素的更改,获取该更改,处理它,然后才能继续处理数组。 我希望在控制台中看到这个: Preparing "aa
给定以下方法: public async Task DoSomethingAsync() { // do some work await OpenSomeFileAsync();
尝试在 .exports 中运行一个异步函数,获取 Promise,然后在下一个异步函数中使用结果,但由于某种原因,无论等待如何,第二个函数都会在第一个函数之前执行。 销售.js = const sq
谁能解释为什么 c# 5 中的异步函数需要至少有 1 个等待?我找不到明确的理由/解释。 所谓必需,是指当异步函数内部没有任何 await 调用时编译器会发出警告,但不会抛出编译错误。 来自 this
我想用 Mocha 测试异步代码. 我跟着这个教程testing-promises-with-mocha .最后,它说最好的方法是 async/await。 以下是我的代码,我打算将 setTimeo
这个问题在这里已经有了答案: How do yield and await implement flow of control in .NET? (5 个答案) How is resumption
我想在 trait 中编写异步函数,但是因为 async fn in traits 还不被支持,我试图找到等效的方法接口(interface)。这是我在 Rust nightly (2019-01-0
在 node.js 中,我有一个数据库事务,我想在 then 回调中调用一个 async 方法,但我收到错误消息 关键字“等待”已保留。 这是异步 saveImage 函数: const saveIm
我正在包装 AspNet.Identity。但有些事情让我对 TPL 感到困惑。 第一个例子: public virtual async Task RemovePasswordAsync(st
我有三个 showDialog 示例。我认为 _showAlert1 是正确的,但它使用 2 个函数来实现它。 _showAlert2 也有效,但我认为它不正确,因为我认为 showDialog 是异
我正在编写一个应该尽可能快地执行所有异步函数的函数,但是,它们中只有 5 个可以同时运行。 我想使用 Promise.race 来实现,所以实现不是最好的。问题是代码执行不会在 await 处停止。我
在 Scala 和其他编程语言中,可以使用 Futures 和 Await。 (在实际代码中,会使用例如 zip+map 而不是 Await) def b1() = Future { 1 } def
这个问题在这里已经有了答案: At the end of an async method, should I return or await? (2 个回答) 8年前关闭。 我做了一些阅读,并认为我已
我知道这是一个非常开放的问题,我深表歉意。 我可以看到 Await.ready返回 Awaitable.type而 Await.result返回 T但我仍然混淆他们。 两者有什么区别? 一个是阻塞的,
为什么等待者(GetAwaiter - 使类可等待)是结构而不是类。使用类有什么坏处吗? public struct ConfiguredTaskAwaiter : ICriticalNotifyCo
这个问题在这里已经有了答案: Why doesn't Scala's Future have a .get / get(maxDuration) method, forcing us to resor
async/await 链中的所有函数都必须使用 async/await 关键字吗? async function one() { return await fetch(.....); } asy
点击组件的按钮时将执行以下方法。 async onClickButton() { await this.shoppingCartService.add(this.selectedOffer);
它似乎被记录在案的唯一地方是 this issue thread和 the actual specification .但是,删除的原因并没有在我能找到的任何地方发布。 新的推荐方式似乎是await
为什么使用 await 需要将其外部函数声明为 async? 例如,为什么这个 mongoose 语句需要它所在的函数来返回一个 promise? async function middleware(
我是一名优秀的程序员,十分优秀!