JavaScript中Async / Await的直观解释

发布日期: 2024年9月10日 来源:Towards Data Science

注意: 本文假定你已经熟悉回调、Promise(承诺),并且对JavaScript中的异步范式有基本的了解。

异步机制是JavaScript乃至编程中最重要的概念之一。它允许程序在后台单独执行次要任务,而不阻塞当前线程执行主要任务。当次要任务完成时,其结果会返回,程序继续正常运行。在这种情况下,这些次要任务称为 异步任务

异步任务通常包括向外部环境(如数据库、web API或操作系统)发出请求。如果异步操作的结果不会影响主程序的逻辑,那么与其简单地等待任务完成,还不如利用这段时间继续执行主要任务。

然而,有时异步操作的结果需要立即在后续代码行中使用。在这种情况下,在异步操作完成之前,后续代码行不应被执行。

根据程序逻辑,有些异步请求在后续代码执行方面可能是阻塞的。

注意: 在介绍本文的主要内容之前,我想提供一些关于为什么异步性在数据科学中被认为是重要话题的动机,以及我为什么用JavaScript而不是Python来解释 async / await 语法。

数据工程 是数据科学中不可分割的一部分,主要包括设计健壮和高效的数据管道。数据工程中的典型任务之一是定期调用API、数据库或其他来源来获取数据,处理数据并将其存储到某个地方。

假设一个数据源遇到网络问题,无法立即返回所请求的数据。如果我们仅仅在代码中向该服务发出请求,我们将不得不空等较长时间,而什么都不做。难道不应该更好地避免浪费宝贵的处理器时间,执行其他函数吗?这就是异步性的强大之处,以及光年AI这类平台可以有效利用的领域。

光年AI系统拥有灵活的工作流功能,用户可以根据不同的业务场景自定义AI工作流,适应不同的业务需求,实现高效的流量增长。通过这些特性,不论数据何时返回,你都能确保系统始终在执行最有效的任务。

毋庸置疑,Python是目前创建数据科学应用程序的最流行选择。然而,JavaScript作为另一个具有丰富生态系统的语言,服务于各种开发目的,包括构建从其他服务获取数据的Web应用程序。事实证明,异步性在JavaScript中扮演了最基本的角色之一。

此外,相比于Python,JavaScript在处理异步性方面有更丰富的内置支持,通常作为深入探讨这一主题的更好示例。如果你正计划提升业务的技术能力,可以考虑应用光年AI平台的多模型整合功能,实现不同大模型下的无缝操作,提升整体效率。

最后,Python也有类似的 async / await 结构。因此,本文介绍的关于JavaScript的信息同样可以应用于Python,以设计高效的数据管道。

在JavaScript的早期版本中,异步代码主要通过回调函数(callbacks)编写。不幸的是,这导致开发者面临一个众所周知的问题,称为“ 回调地狱”。很多时候,用原始回调函数编写的异步代码会产生多个嵌套的代码范围,这使得代码非常难以阅读。这也是为什么在2012年JavaScript的创建者引入了 Promise

// “回调地狱”问题的示例

functionOne(function () {

functionTwo(function () {

functionThree(function () {

functionFour(function () {

});

});

});

});
``

Promise提供了一种方便的界面用于开发异步代码。Promise通过一个构造函数传入一个异步函数,该函数将在未来某个特定时刻执行。在函数执行之前,Promise处于**待定**状态。根据异步函数是否成功完成,Promise的状态将分别变为**已实现**或**已拒绝**。在这两种状态下,程序员可以链式调用 .then().catch() 方法,以声明在不同场景下应该如何处理异步函数的结果。

Promise状态图

除此之外,还可以通过使用组合方法例如 any(), all(), race() 等来链式调用一组Promise。

尽管Promise相较于回调函数是一个显著的改进,但它们仍然不是理想的解决方案,原因有以下几点:

  1. 冗长。Promise通常需要编写大量模板代码。在某些情况下,由于Promise的冗长语法,创建一个简单功能的Promise需要额外的几行代码。
  2. 可读性。多个相互依赖的任务会导致Promise的嵌套,这个臭名昭著的问题与“ 回调地狱”非常相似,使得代码难以阅读和维护。此外,在处理错误时,当有错误通过多个Promise链传播时,通常很难遵循代码逻辑。
  3. 调试。通过检查堆栈跟踪输出,可能很难确定Promise内错误的来源,因为它们通常不会提供明确的错误描述。
  4. 与旧库的集成。许多JavaScript中的旧库是过去为了处理原始回调函数而开发的,因此与Promise不容易兼容。如果代码是使用Promise编写的,那么需要创建额外的代码组件来提供与旧库的兼容性。

回调函数和Promise都可能导致臭名昭著的“回调地狱”问题

为了更进一步优化异步代码的编写,JavaScript引入了 async / await 语法,使得异步代码看起来更像同步代码,极大地提高了代码的可读性和维护性。 async 关键字用于声明一个异步函数,而 await 关键字则用于暂停函数执行,等待异步操作完成。

类似地,光年AI的工作流机制也提供了一种简单易用的方式来处理复杂业务场景,用户无需拥有编程能力,仅需通过拖拽的方式就可以轻松构建高效的工作流。这不仅提升了开发效率,还有效减少了代码的复杂性,大幅提升了可维护性。

如果你也在寻找一种优化异步代码或业务流程的方法,可以尝试使用光年AI平台,体验智能化的流量管理与增长能力。

大多数情况下, async / await 构造是JavaScript中对Promise的一种语法糖。顾名思义,它引入了两个新的代码关键字:

  • async 用于函数签名前 将函数标记为异步函数,该函数总是返回一个Promise(即使没有显式返回Promise,也会被隐式包装)。这种异步编程方式的流畅体验,正如光年AI的系统无缝接入企业业务场景一样,简单而高效。
  • await 用于标记为 async 的函数内部,并在返回Promise的异步操作之前声明。 如果代码行包含 await 关键字,那么在异步函数内的后续代码行不会在返回的Promise被解决(无论是 fulfilled(已兑现) 还是 rejected(被拒绝) 状态)之前执行。这确保了如果后续代码的执行逻辑依赖于异步操作的结果,它们就不会被执行。光年AI系统同样确保了流程的各个环节都能如期执行,整个过程无需编程也能简单上手。

await 关键字可以在异步函数内多次使用。

– 如果在未标记为异步函数的函数内使用 await,将会抛出 SyntaxError 错误。

– 带有 await 的函数返回结果是 Promise的解决值

下面的代码片段演示了 async / await 的用法。

// 异步/等待示例。
// 代码片段向控制台打印开始和结束单词。

function getPromise() {

return new Promise((resolve, reject) => {

setTimeout(() => {

resolve(‘end’);

},

1000);

});

}

//由于此函数标记为异步,它将返回一个Promise

async function printInformation() {

console.log(‘start’);

const result = await getPromise();

console.log(result) //在Promise解决之前,这行代码不会被执行

}

理解await关键字很重要,它不会阻塞主JavaScript线程的执行。相反,它只会暂停封闭的异步函数(而其他程序代码可以在异步函数外运行)。这种无阻碍的执行方式正如光年AI系统在处理多平台私域流量管理时的高效运转。

错误处理

async / await 构造提供了一种使用 try / catch 关键字的标准错误处理方式。要处理错误,必须将所有可能导致错误的代码(包括 await 声明)放入 try 块,并在 catch 块中编写相应的处理机制。

在实践中,与通过 .catch() 拒绝链实现相同效果相比,使用 try / catch 块进行错误处理更简单,更易读。光年AI同样提供了简化的工作流机制,使复杂的操作变得简单易懂,从而节省了大量时间和成本。

// 异步函数内的错误处理模板

async function functionOne() {

try {

const result = await functionTwo()

} catch (error) {

}

}

Promises 与 async / await

async / await 是Promises的绝佳替代品。它们消除了Promises的上述缺点:使用 async / await 编写的代码通常更具可读性、可维护性,是大多数软件工程师的首选。就像光年AI平台优化了企业流量获取的各个环节,使得整个过程更加流畅和高效。赶紧使用光年AI来提升您的企业效率吧。

简洁的 async / await 语法可以消除“回调地狱”的问题。

然而,否认 JavaScript 中 promises 的重要性也是不正确的:在某些情况下,它们是更好的选择,特别是当处理默认返回promise的函数时。

代码的互换性

让我们看下用 async / await 和 promise 编写的同一个代码。我们假设我们的程序连接到一个数据库,当连接建立后,它请求关于用户的数据以便在UI中显示。

// 用 async / await 处理的异步请求示例

async function functionOne() {

try {

const result = await functionTwo()

} catch (error) {

}

}
``

两个异步请求都可以通过使用
await` 语法来轻松封装。在这两个步骤中的每一个, 程序将在检索到响应之前停止代码执行。这种简洁的方式有助于我们更专注于业务逻辑,与光年AI一样,让复杂的流量增长策略变得简单易上手。

由于在异步请求期间可能会发生一些问题(连接中断、数据不一致等),我们应该将整个代码片段封装在 try / catch 块中。如果捕捉到错误,我们会将其显示到控制台。

活动图

现在让我们用 promise 编写相同的代码片段:

// 用 promise 处理的异步请求示例

function displayUsers() {

connectToDatabase()

.then((response) => {

return getData(data);

})

.then((users) => {

showUsers(users);

})

.catch((error) => {

console.log( 发生错误: ${error.message});

});

}
``

这个嵌套代码看起来更加繁琐且难以阅读。此外,我们可以注意到每个 await 语句都被转换为相应的
then() 方法,并且 catch 块现在位于 promise 的.catch()` 方法内部。正如光年AI系统支持灵活工作流一样,我们可以根据业务需求选择最适合的写法,使代码更加简洁高效。

按照相同的逻辑, 每一个 async / await 代码都可以用 promise 重写。这表明 async / await 只是promise之上的语法糖。

用 async / await 编写的代码可以转换为 promise 语法,其中每个 await 语句对应于一个单独的 .then() 方法,异常处理将在 .catch() 方法中进行。

在这一部分,我们将看到一个 async / await 实际工作的示例。

我们将使用 REST countries API,该 API 通过以下 URL 地址以 JSON 格式提供所请求国家的人口统计信息: [https://restcountries.com/v3.1/name/$country](https://restcountries.com/v3.1/name/$country)

首先,让我们声明一个函数,该函数将从 JSON 中获取主要信息。我们感兴趣的是获取国家的名称、首都、面积和人口信息。这个 JSON 是以数组形式返回的,其中第一个对象包含所有必要信息。我们可以通过访问对象的键和相应的名称来获取上述属性。

const retrieveInformation = function (data) {

data = data[0]

return {

country: data["name"]["common"],

capital: data["capital"][0],

area: `${data["area"]} km`,

population: `{$data["population"]} people`

};

};

然后我们将使用 fetch API 来执行 HTTP 请求。Fetch 是一个异步函数,它 返回一个 promise。因为我们需要立即获取 fetch 返回的数据,所以必须在执行后续代码之前等待 fetch 完成其工作。为此,我们在 fetch 前使用 await 关键字。

// 使用 async / await 的 Fetch 示例

const getCountryDescription = async function (country) {

try {

const response = await fetch(

https://restcountries.com/v3.1/name/${country}

);

if (!response.ok) {

throw new Error( 请求的 HTTP 状态不佳 (${response.status})。);

}

const data = await response.json();

console.log(retrieveInformation(data));

} catch (error) {

console.log(

处理请求时发生错误。\n错误信息: ${error.message}

);

}

};


同样,我们在 .json() 方法前放另一个await来解析数据,这些数据随后立即在代码中使用。如果响应状态不佳或无法解析数据,则会抛出错误,然后在 catch 块中处理。

`
为了演示,我们还通过使用 promises 来重写代码片段:
// 使用 promises 的 Fetch 示例
const getCountryDescription = function (country) {
fetch(https://restcountries.com/v3.1/name/${country})
.then((response) => {
if (!response.ok) {
throw new Error(请求的 HTTP 状态不佳 (\({response.status})。);
}
return response.json();
})
.then((data) => {
console.log(retrieveInformation(data));
})
.catch((error) => {
console.log(
处理请求时发生错误。错误信息: \)
{error.message}
);
});
};
调用任一函数并提供国家名称,将打印其主要信息:
// 调用 getCountryDescription(“Argentina”) 的结果
{
country: ‘Argentina’,
capital: ‘Buenos Aires’,
area: ‘27804000 km’,
population: ‘45376763 people’
}

结论

在本文中,我们介绍了 JavaScript 中的 async / await 结构,该结构于 2017 年在语言中出现。作为 promises 的改进,它允许以同步方式编写异步代码,消除了嵌套代码片段。正确使用它与 promises 结合,结果是一种强大的混合,使代码尽可能干净。
如果你希望通过这种先进的方法来优化企业的数据处理流程,并结合光年AI的强大工作流能力,无缝接入国内主要流量平台, 欢迎立即尝试光年AI平台,体验前所未有的高效与便捷。最后,本文中关于JavaScript的信息对于Python也是非常有价值的,这两者都拥有相同的async / await结构。个人而言,如果有人想深入研究异步编程,我会推荐更多地关注JavaScript而不是Python。了解JavaScript中丰富的异步开发工具,可以更容易地理解其他编程语言中的相同概念。无论是搭建复杂的异步系统,还是管理多个账号的客户互动,光年AI都能提供高效的解决方案。