请你谈谈:async与await是如何控制异步操作的执行顺序

news/2024/9/18 12:06:54/ 标签: 前端

async/await 是 JavaScript 中用于处理异步操作的一种语法糖,它使得异步代码的编写、阅读和维护变得更加容易和直观。asyncawait 关键字是在 ES2017(ES8)中引入的,旨在简化基于 Promise 的异步操作。

1 async

  • async 是一个函数声明的前缀,用于指定一个函数是异步的(promise.then等回调)。这意味着函数内部可能包含异步操作,如网络请求、文件读取等。
  • 当你将一个函数声明为 async 时,该函数会自动返回一个 Promise。如果函数正常结束(即没有显式返回 Promise 或其他值),它将返回一个解析为 undefined 的 Promise。如果函数通过 return 语句返回了一个值,那么返回的 Promise 将被解析为该值。如果函数内部抛出了异常,返回的 Promise 将被拒绝(rejected),异常值作为拒绝的原因。

当然可以,以下是分别举例说明这三种情况的代码:

1. 函数正常结束,没有显式返回 Promise 或其他值
async function asyncFunctionWithoutReturn() {// 函数体内执行一些操作,但不显式返回任何值console.log('函数执行了,但没有返回值');// 由于没有显式返回,所以函数将隐式返回一个解析为 undefined 的 Promise
}let promise = asyncFunctionWithoutReturn()
promise.then(result => {console.log(result); // 输出:undefined
}).catch(error => {console.error(error); // 这里不会被调用,因为没有抛出异常
});
2. 函数通过 return 语句返回了一个值
async function asyncFunctionWithReturnValue() {// 函数体内执行一些操作,并通过 return 语句返回一个值console.log('函数执行了,并返回了一个值');return 'Hello, async!';// 由于有显式返回,所以函数将返回一个解析为 'Hello, async!' 的 Promise
}let promise = asyncFunctionWithReturnValue()
promise.then(result => {console.log(result); // 输出:Hello, async!
}).catch(error => {console.error(error); // 这里不会被调用,因为没有抛出异常
});
3. 函数内部抛出了异常
async function asyncFunctionThrowsError() {// 函数体内执行一些操作,并抛出一个异常console.log('函数执行中...');throw new Error('出错了!');// 由于抛出了异常,所以函数将返回一个被拒绝的 Promise,异常值作为拒绝的原因
}
let promise = asyncFunctionThrowsError()
promise.then(result => {// 这里不会被调用,因为 Promise 被拒绝了console.log(result);
}).catch(error => {console.error(error.message); // 输出:Error: 出错了!// 捕获到异常,并可以在这里处理它
});

在这三个例子中,我们分别展示了当一个函数被声明为 async 时,它如何根据函数体内的不同情况自动返回一个 Promise。第一个例子展示了没有显式返回任何值时的情况,第二个例子展示了通过 return 语句返回一个值的情况,第三个例子展示了函数内部抛出异常时的情况。这些例子清楚地说明了 async 函数如何处理其返回值和异常。

2 await

await 关键字是 JavaScript 中处理异步操作的一个非常强大的工具,但它确实有一些限制和使用场景:

  1. 只能在 async 函数内部使用await 只能在被 async 关键字声明的函数或方法内部使用。这意味着你不能在普通的同步函数或全局作用域中使用 await

  2. 等待 Promise 完成await 会暂停 async 函数的执行,直到它等待的 Promise 被解决(fulfilled)或拒绝(rejected)。这使得你可以以类似于同步代码的方式来编写异步逻辑。

  3. 返回 Promise 解决的结果:当 await 等待的 Promise 被解决时,await 表达式会返回 Promise 解决的值。这个值可以被赋给变量,或者用于进一步的计算。

  4. 异常处理:如果 await 等待的 Promise 被拒绝,那么 await 表达式会抛出一个异常。这个异常可以被 async 函数内部的 try...catch 语句捕获,就像处理同步代码中的异常一样。

  5. 提升代码可读性:使用 async/await 可以使异步代码更加清晰和易于理解,因为它允许你以更接近同步代码的方式来编写和执行异步逻辑。

  6. 不阻塞主线程:尽管 await 会暂停 async 函数的执行,但它不会阻塞整个 JavaScript 运行时或主线程。JavaScript 运行时可以继续执行其他任务,如事件处理、定时器回调等,直到等待的 Promise 完成。

在 JavaScript 中,await 关键字用于等待一个 Promise 完成,并且它只能在 async 函数内部使用。await 的行为取决于它右侧的表达式:

  1. 如果表达式是 Promise 对象

    • await 会暂停 async 函数的执行,直到该 Promise 被解决(fulfilled)或拒绝(rejected)。
    • 如果 Promise 被成功解决,await 会返回解决的值。
      在这里插入图片描述 - 如果 Promise 被拒绝,await 会抛出一个异常,这个异常可以通过 try...catch 结构来捕获和处理。在这里插入图片描述
  2. 如果表达式不是 Promise 对象

    • await 会立即返回该表达式的值,而不会等待任何异步操作完成。这是因为非 Promise 类型的值被视为已经解决的 Promise(即其值已经可用)。

3 async与await结合实践

当然,下面是一个典型的回调地狱(Callback Hell)的例子,这个例子通常出现在处理多个异步操作并且每个操作的结果都是下一个操作所需的输入时。我们将使用Node.js的fs模块来模拟文件读取操作,尽管在Node.js v10及更高版本中,推荐使用fs.promises API或util.promisify来避免回调地狱。

回调地狱的例子

假设我们需要按顺序读取三个文件,并将它们的内容拼接起来。使用传统的回调方式,代码可能会像这样:

const fs = require('fs');fs.readFile('file1.txt', 'utf8', (err, data1) => {if (err) throw err;fs.readFile('file2.txt', 'utf8', (err, data2) => {if (err) throw err;fs.readFile('file3.txt', 'utf8', (err, data3) => {if (err) throw err;console.log(data1 + data2 + data3);});});
});

为了解决这个问题,我们可以使用async/awaitfs.promises API(或在较旧版本的Node.js中使用util.promisify转换的fs.readFile)。下面是使用fs.promises API的示例:

const fs = require('fs').promises;async function readFileConcatenate() {try {const data1 = await fs.readFile('file1.txt', 'utf8');const data2 = await fs.readFile('file2.txt', 'utf8');const data3 = await fs.readFile('file3.txt', 'utf8');console.log(data1 + data2 + data3);} catch (error) {console.error('Error reading file:', error);}
}// 调用函数
readFileConcatenate();

在这个async函数中,我们使用了await来等待每个fs.readFile调用的结果。由于await只能在async函数内部使用,因此我们将文件读取逻辑封装在了一个名为readFileConcatenateasync函数中。这种方式使得代码更加清晰和易于维护,同时避免了回调地狱的问题。

如果你正在使用Node.js的较旧版本,并且fs模块没有内置的promises API,你可以使用util.promisify来转换fs.readFile

const fs = require('fs');
const util = require('util');// 转换fs.readFile为返回Promise的函数
const readFile = util.promisify(fs.readFile);async function readFileConcatenate() {try {const data1 = await readFile('file1.txt', 'utf8');const data2 = await readFile('file2.txt', 'utf8');const data3 = await readFile('file3.txt', 'utf8');console.log(data1 + data2 + data3);} catch (error) {console.error('Error reading file:', error);}
}// 调用函数
readFileConcatenate();

这种方式同样有效,并且可以在不支持fs.promises API的Node.js版本中使用。

要使用asyncawait来确保fun1fun2fun3这三个方法按顺序调用,并且每个方法内部都执行一个异步的AJAX请求,你可以首先确保这三个方法都返回Promise。然后,在另一个方法中,你可以使用await来等待每个方法完成后再继续执行下一个。

以下是一个简单的示例,展示了如何实现这一点:

// 假设我们使用fetch API来模拟AJAX请求(你也可以使用XMLHttpRequest或其他库)// fun1 方法,返回一个Promise
function fun1() {return new Promise((resolve, reject) => {// 模拟异步请求setTimeout(() => {console.log('fun1 执行完毕');resolve('fun1的结果');}, 1000); // 假设请求耗时1秒});
}// fun2 方法,同样返回一个Promise
function fun2() {return new Promise((resolve, reject) => {// 模拟异步请求setTimeout(() => {console.log('fun2 执行完毕');resolve('fun2的结果');}, 1000); // 假设请求耗时1秒});
}// fun3 方法,也返回一个Promise
function fun3() {return new Promise((resolve, reject) => {// 模拟异步请求setTimeout(() => {console.log('fun3 执行完毕');resolve('fun3的结果');}, 1000); // 假设请求耗时1秒});
}// 调用这三个方法的函数,使用async和await来确保顺序执行
async function executeFunctionsInOrder() {try {const result1 = await fun1(); // 等待fun1完成console.log('fun1的返回结果:', result1);const result2 = await fun2(); // 等待fun2完成console.log('fun2的返回结果:', result2);const result3 = await fun3(); // 等待fun3完成console.log('fun3的返回结果:', result3);} catch (error) {// 如果有任何一个函数出错,这里会捕获到错误console.error('执行过程中发生错误:', error);}
}// 调用函数
executeFunctionsInOrder();

在这个例子中,fun1fun2fun3都使用了setTimeout来模拟异步操作(例如AJAX请求)。每个函数都返回一个Promise,该Promise在异步操作完成后被解决(resolve)。在executeFunctionsInOrder函数中,我们使用了async关键字来标记该函数为异步函数,这样我们就可以在函数体内使用await来等待每个异步操作的完成。注意,await只能用在async函数内部。

运行这段代码,你会看到控制台按顺序输出了fun1fun2fun3的执行结果,每个之间大约间隔1秒(由setTimeout设置)。


http://www.ppmy.cn/news/1515514.html

相关文章

【机器学习】数据预处理、特征缩放以及有偏分布的基本概念

引言 数据预处理是机器学习过程中的一个关键步骤,它涉及对原始数据进行清洗、转换和重塑,以提高模型的性能和准确性 文章目录 引言一、数据预处理1.1 定义1.2 步骤1.2.1 数据清洗1.2.2 数据转换1.2.3 数据重塑1.2.4 数据分割1.2.5 数据增强1.2.6 处理不平…

[Linux#47][网络] 网络协议 | TCP/IP模型 | 以太网通信

目录 1.网络协议 2.协议分层 2.1 OSI七层模型 2.2TCP/IP五层(四层)模型 2.3 以太网通信 1.网络协议 "协议"本质就是一种约定 计算机之间的传输媒介是光信号和电信号. 通过 "频率" 和 "强弱" 来表示 0 和 1 这样的 信息. 要想传递各种不同…

C/C++语言基础--指针三大专题详解3,完结篇(包括指针做函数参数,函数指针,回调函数,左右法则分析复杂指针等)

本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 指针是C/C的灵魂,和内存地址相关联,运行的时候速度快,但是同时也有很多细节和规范要注意的,毕竟内存泄漏是很恐怖的指针打算分三篇文章进行讲解,本…

stm32-USB-1

1. USB简介 USB, 英文全称:Universal Serial Bus,即通用串行总线 USB提供适合各种应用的传输协议,而且协议标准向下兼容 优缺点 2. USB2.0拓扑结构 USB是一种主从结构的系统,数据交换只能发生在主从设备之间&#…

s-nail最新配置格式

默认的s-nail配置文件运行时会报警告 s-nail: Warning: variable superseded or obsoleted: bsdannounce s-nail: Obsoletion warning: command will be removed: fwdretain可以考虑注释掉s-nail.rc文件的bsdannounce/fwdretain命令 使用网上的邮件配置虽然可以运行 set fro…

AI 时代的编程革命:如何在挑战中抓住机遇?

AI 发展对软件开发的挑战与机遇:程序员应对策略 随着人工智能(AI)技术的快速进步,软件开发领域正经历深刻的变革。AI 不仅改变了编程的方式,也对程序员的职业发展产生了重要影响。在这个背景下,我们既看到…

PD取电快充协议方案

PD快充协议是通过调整电压和电流来提供不同的充电功率。它采用了一种基于USB-C端口的通信协议,实现了充电器于设备之间的信息交换。在充电过程中设备会向充电器发出请求,要求提供不同的电压和电流,充电器接收到请求后,会根据设备的…

多商户小程序审核存在商户入口无法通过

小程序拒绝如下: 需要注意的地方如下: 关闭店铺展示关闭商户入驻关闭diy中的申请入口、店铺街入口等关闭个人中心广告的申请入口关闭分销关闭支付宝

如何利用命令模式实现一个手游后端架构

命令模式(Command Pattern)是一种行为设计模式,它允许将请求封装为对象,从而使用不同的请求、队列、日志来参数化其他对象。命令模式也支持可撤销的操作。虽然命令模式在图形用户界面(GUI)编程中最为常见&a…

【Harmony OS 4.0】自定义组件 —— @Component 装饰器

Component 装饰器用于装饰 struct 关键字声明的数据结构。struct 被 Component 装饰后才具有组件化的能力。 1.2 具有以下特点: 1.2.1 可组合:允许开发者组合使用多个系统组件,及其属性和方法,实现UI的复用。 1.2.2 可重用&#x…

20L水箱植保无人机技术详解

1. 性能与载重 高效作业能力 本款20L水箱植保无人机专为大面积农田作业设计,具备出色的性能与载重能力。其最大载重量可达20kg,不仅轻松搭载20L的水箱及药液,还能根据实际作业需求配置额外的传感器、摄像头等设备,实现多功能集成…

影刀上传文件api

影刀上传文件api # 文件上传 - 影刀帮助中心 import requestsyingdao_Info{"accessKeyId":"XXX","accessKeySecret":"XXX","accountName": xmbjywz,"robotUuid":"XXX","file_path":"D…

Java实现数据库数据到Excel的高效导出

在数据处理和分析工作中,经常需要将数据库中的数据导出到Excel文件中。本文将提供一个Java实现的示例,展示如何边从数据库读取数据,边将其写入Excel文件,同时注重内存效率。 环境配置: Java 1.8 或更高版本MySQL 5.7…

【设计模式】模块模式和桥接模式

模块模式 模块化模式最初被定义为在传统软件工程中为类提供私有和公共封装的一种方法。 能够使一个单独的对象拥有公共/私有的方法和变量,从而屏蔽来自全局作用域的特殊部分。这可以减少我们的函数名与在页面中其他脚本区域内定义的函数名冲突的可能性。 闭包&am…

Java | Leetcode Java题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; class Solution {public boolean canMeasureWater(int x, int y, int z) {if (x y < z) {return false;}if (x 0 || y 0) {return z 0 || x y z;}return z % gcd(x, y) 0;}public int gcd(int x, int y) {int remainder x % y;w…

SpringBoot天猫商城基于前后端分离+SpringBoot+BootStrap、Vue.js、JQuery+JPA+Redis

SpringBoot天猫商城整站 一、项目介绍和演示 SPRINGBOOT天猫整站&#xff0c;基于 前后端分离思想&#xff0c; 由于该商城高并发的特点&#xff0c;后端框架便使用了方便维护的 SpringM VC、SpringBoot框架&#xff0c;而前端框架则选择了主流的BootStrap、Vue.js&#xff0c;…

C#高效异步文件监控与日志记录工具

优势 异步处理&#xff1a;提高了文件变化处理的效率&#xff0c;避免了阻塞主线程。线程安全&#xff1a;使用了线程安全的队列来避免多线程环境下的竞态条件。日志记录&#xff1a;异步日志记录减少了对主线程的干扰&#xff0c;并且能够处理大量事件。灵活配置&#xff1a;…

Redis4

全局唯一ID package com.hmdp.utils;import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component;import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter;Compone…

第三课《排序》

前言 排序是将一组数据&#xff0c;按照指定的顺序或要求来进行排列的过程。是数据结构相关课程和内容较为重要和核心的内容之一&#xff0c;常常作为考试题和面试题目来考察学生和面试者&#xff0c;因此熟练掌握经典的排序算法原理和代码实现是非常重要的 本文介绍了几大较为…

JavaScript 文件上传详解与实现

文件上传是 Web 开发中常见的功能之一&#xff0c;几乎所有的 Web 应用都会涉及到上传文件&#xff0c;如上传图片、视频、文档等。 一、基本文件上传实现 1.1 HTML 表单元素 文件上传通常通过 <input> 元素的 type"file" 来实现。以下是一个简单的 HTML 文…