JavaScript - 异步编程

1. 前言

在 JavaScript 中,异步编程是一种处理需要等待操作(如网络请求、文件读取或计时器)的编程方式。由于 JavaScript 是单线程的,意味着它一次只能执行一个任务。异步编程允许你在等待某些操作完成时,继续执行其他任务而不会阻塞主线程。

2. 异步函数

异步函数,当你调用它时,它不会立即阻塞代码的执行。程序会立刻继续执行下面的代码。

异步的本质是因为底层机制:像 setTimeoutPromiseasync/await 等等。这些机制让 JavaScript 不需要等待耗时操作的结果,而可以继续执行其他代码,回调函数是在异步操作完成后触发的。

console.log("I'm going to make dinner");
setTimeOut(()=>{Console.log('My dinner is ready');},1000);
console.log('I'm going to watch TV');//I'm going to make dinner
//I'm going to watch TV
//My dinner is ready

3. 回调函数

回调函数是一个作为参数传递给另一个函数的函数,目的是在特定操作完成后执行。这是一种在异步编程中处理操作完成的方式。

将回调函数看作一个参数,在另一个函数中进行调用就可以很好的去理解。

在异步操作中,我们可以将回调函数当做一个通知,异步函数调用完毕后,通知操作者

function huidiao(introduce)
{let name = 'Bob';introduce(name); //在此函数中调用
};function introduce(name)
{console.log(name);
};
function doStep1(init, callback) {const result = init + 1;callback(result);
}
function doStep2(init, callback) {const result = init + 2;callback(result);
}
function doStep3(init, callback) {const result = init + 3;callback(result);
}
function doOperation() {doStep1(0, (result1) => {doStep2(result1, (result2) => {doStep3(result2, (result3) => {console.log(`结果:${result3}`);});});});
}
doOperation();

doOperation 函数依次调用doStep1doStep2doStep3,并在每一步完成后,将结果传递给下一步的回调函数。

在上述代码中,(result1) ==> {} 表示回调函数,在JavaScript独有的箭头函数

4. 箭头函数

(result) => {// 函数体
}

其中result代表函数所接受的参数

{}中运行的结构自动作为返回值
eg:

const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出:5

5. Promise类型

5.1 创建Promise

const promise = new Promise((resolve, reject) => {// 异步操作if (/* 操作成功 */) {resolve('成功的结果');} else {reject('失败的原因');}
});

使用我们的异步函数,最后异步函数所返回的类型为Promise类型

首先,Promise 有三种状态:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。这是调用 fetch() 返回 Promise 时的状态,fetch()是一个异步函数,不干扰主线程,此时请求还在进行中。
  • 已兑现(fulfilled):意味着操作成功完成。当 Promise 完成时,then() 处理函数(类似于我们的回调函数,异步任务处理完成后)被调用。
  • 已拒绝(rejected):意味着操作失败。当一个 Promise 失败时,它的 catch() 处理函数被调用。catch函数进行处理错误。

5.2 fetch()函数

使用fetch异步函数,会得到Promise返回对象

Promise返回对象:Promise { <state>: "pending" }。这告诉我们有一个 Promise 对象,它有一个 state属性,值是 "pending""pending" 状态意味着操作仍在进行中。所有的异步函数返回对象都是Promise类型。

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);console.log(fetchPromise);fetchPromise.then((response) => {console.log(`已收到响应:${response.status}`);
});console.log("已发送请求……");

上述代码使用fetch函数,将内容获取操作不占用主线程,仍然可以继续运行主线程的代码。

5.3 .then

获取到返回的Promise对象之后,使用Promise对象参数then,类似于我们的回调函数,异步操作完成之后我们需要干什么的编写。

promise.then会判断异步操作成功后,自动向回调函数传入response返回值

fetchPromise.then((response) => response.json()).then((data) => {console.log(data[0].name);});

与response所结合完成我们的异步操作

上述代码所示,两个then也是同步所进行的。

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);fetchPromise.then((response) => {const jsonPromise = response.json();jsonPromise.then((json) => {console.log(json[0].name);});
});

5.4 .catch

你调用它并传入一个处理函数。然后,当异步操作成功时,传递给 then() 的处理函数被调用,而当异步操作失败时,传递给 catch() 的处理函数被调用。 

如果将 catch() 添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。于是,我们就可以将一个操作实现为几个连续的异步函数调用,并在一个地方处理所有错误。

const fetchPromise = fetch("bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);fetchPromise.then((response) => {if (!response.ok) {throw new Error(`HTTP 请求错误:${response.status}`);}return response.json();}).then((json) => {console.log(json[0].name);}).catch((error) => {console.error(`无法获取产品列表:${error}`);});

5.5 .final

无论获取是正确还是错误,异步操作仍然执行的部分。 

5.6 Promise.all()

处理多个异步函数时可以用到。

当所有的异步函数处理完毕后,使用Promise.all(异步函数返回对象),对所有的返回对象做进一步处理,通常搭配循环,返回的responses是一个列表。

注意:处理过程是同步运行过程

当有一个异步未完成时,抛出错误。

const fetchPromise1 = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
const fetchPromise2 = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/not-found",
);
const fetchPromise3 = fetch("https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
);Promise.all([fetchPromise1, fetchPromise2, fetchPromise3]).then((responses) => {for (const response of responses) {console.log(`${response.url}:${response.status}`);}}).catch((error) => {console.error(`获取失败:${error}`);});

6. async&await关键字

使用async语法糖可以更加方便快捷的去创建异步函数

在函数内部也是通过同步的形式进行运行,但与主线程的工作不相互冲突。

async function OK()
{//异步函数
}

定义好异步函数之后,可以在转换线程的代码前声明await,这使得代码在该点上等待,直到 Promise 被完成,这时 Promise 的响应被当作返回值,或者被拒绝的响应被作为错误抛出。

async function time_end
{try {const response = await fetch('url');}const json = response.json();catch(error){console.error(`${error}`)};
}

await fetch所返回的是一个完整的response对象而不是promise对象。

await相当于自动将Promise对象转为response对象

await 强制异步操作以串联的方式完成。如果下一个操作的结果取决于上一个操作的结果,这是必要的,但如果不是这样,像 Promise.all() 这样的操作会有更好的性能。

在异步函数中使用循环

注意:在异步函数中是不能使用foreach的

async function yibu()
{    const promises = [program1(),program2(),program3()];for wait (let I of promises){}
}

7. 小例子:闹钟警报器

7.1 基于setTimeout函数

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title></title></head><body><button id='set-alarm'>Set alarm</button><div id='output'></div></body><script>const output = document.querySelector('#output');const button = document.querySelector('#set-alarm');function setAlarm(){window.setTimeout(()=>{output.textContent = 'Wake up';},2000);}button.addEventListener("click", setAlarm);</script>
</html>

7.2 基于async&await

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title></title></head><body>Name: <input type='text' id='name'><br>Delay:  <input type='number' id='delay'><button id='set-alarm'>Set alarm</button><div id='output'></div></body><script>const output = document.querySelector('#output');const button = document.querySelector('#set-alarm');const delay = document.querySelector('#delay');const name = document.querySelector('#name');function alarm(person, delay) {return new Promise((resolve, reject) => { //函数返回一个Promise对象if (delay < 0) {reject(new Error('Alarm delay must not be negative'));return;  // 需要 return 以防止继续执行后面的代码}// 设置定时器来触发 resolvewindow.setTimeout(() => {resolve(`Wake up, ${person}!`);}, delay);  // 注意这里没有括号错位});}button.addEventListener('click', async () => {try {const message = await alarm(name.value, parseInt(delay.value));  // 确保 delay.value 是数字output.textContent = message;} catch (error) {output.textContent = error.message;  // 如果发生错误,显示错误信息}});</script>
</html>

7.3 resolve参数

通常与reject参数一起搭配使用

它是 Promise 对象的构造函数中的一个参数。resolve 的作用是将 Promise 对象的状态从 待定(pending) 变为 已解决(fulfilled)。这意味着 resolve 函数用于标记异步操作的成功完成,并且可以传递成功的结果。

const myPromise = new Promise((resolve, reject) => {// 模拟异步操作setTimeout(() => {const success = true; // 假设操作成功if (success) {resolve('操作成功'); // 将 Promise 状态变为已解决,并传递结果} else {reject('操作失败'); // 将 Promise 状态变为已拒绝,并传递错误}}, 1000);
});

8. 参考资料

如何实现基于 Promise 的 API - 学习 Web 开发 | MDN

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/140759.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【技术调研】三维(4)-ThreeJs阴影投射、光线投射及案例

阴影投射 阴影是灯光经过物体后产生的&#xff0c;几个关键的设置&#xff1a; 灯光属性设置&#xff1a;.castShadow : Boolean 。此属性设置为 true 灯光将投射阴影。注意&#xff1a;这样做的代价比较高&#xff0c;需要通过调整让阴影看起来正确。 查看 DirectionalLight…

CAD_Electrical 2022使用记录

一、CAD软件实用调整 1、如何调节窗口背景颜色 例如&#xff1a;将图中白色的背景色调节为黑色。 步骤&#xff1a;在CAD空白区域点击右键 -> 点击选项 -> 在显示中点击颜色(窗口元素) -> 将二维模型空间统一背景的颜色修改成需要的颜色 2、如何调节关标大小 例如&a…

如何精确统计Pytorch模型推理时间

文章目录 0 背景1 精确统计方法2 手动synchronize和Event适用场景 0 背景 在分析模型性能时需要精确地统计出模型的推理时间&#xff0c;但仅仅通过在模型推理前后打时间戳然后相减得到的时间其实是Host侧向Device侧下发指令的时间。如下图所示&#xff0c;Host侧下发指令与De…

发展与监管协同发力 人工智能算法领域已形成良好生态

发展与监管协同发力 人工智能算法领域已形成良好生态 近日&#xff0c;全国组织机构统一社会信用代码数据服务中心对国家网信办公示的人工智能领域备案信息进行了详尽的分析&#xff0c;揭示了我国人工智能产业的蓬勃景象。据统计&#xff0c;我国人工智能领域的备案主体遍布各…

MySQL之表的约束

目录 前言 一&#xff1a;空属性 二&#xff1a;默认值 三:列描述 四:zerofill 五:主键 六:自增长 七:唯一键 八&#xff1a;外键 接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言 表中一定要有各种约束&#xff0c;通过约束…

零倾覆力矩点(ZMP)

系列文章目录 前言 在机器人学中&#xff0c;零倾力矩点&#xff08;ZMP&#xff09;是一个特征点&#xff0c;主要用于足式运动。在下文的一些假设中&#xff0c;我们将看到&#xff0c;它非正式地代表了一个系统接触反作用力的结果点。例如&#xff0c;下图中的刚体处于静态平…

【深入理解SpringCloud微服务】深入理解nacos配置中心(四)——配置新增或修改源码分析

【深入理解SpringCloud微服务】深入理解nacos配置中心&#xff08;四&#xff09;——配置新增或修改源码分析 原理回顾源码分析ConfigController#publishConfig()ConfigOperationService#publishConfig()nacos事件监听机制ConfigChangePublisher#notifyConfigChange()NotifyCe…

在 FlexSim 中使用 OpenUSD 分析、可视化和优化现实世界的流程

对于制造和工业企业而言&#xff0c;效率和精度至关重要。为了简化运营、降低成本和提高生产力&#xff0c;各公司正在转向数字孪生和离散事件模拟。 离散事件模拟使制造商能够通过试验不同的输入和行为来优化流程&#xff0c;这些输入和行为可以逐步进行建模和测试。 FlexSi…

6. Transforms的使用(一)--ToTensor()

Transforms的使用&#xff08;一&#xff09; 1.使用ToTensor类将数据转化为Tensor形式 导入需要使用的transforms类 from torchvision import transforms创建ToTensor类的实例 totensor transforms.ToTensor()将读取的图片ndarray数据转化为Tensor数据 img cv.imread(img_p…

Java网络编程 TCP通信(Socket 与 ServerSocket)

1.TCP通信原理 TCP通信涉及两个端点&#xff1a;客户端和服务器。服务器端使用 ServerSocket 监听特定端口&#xff0c;等待客户端的连接请求。客户端使用 Socket 连接到服务器的IP地址和端口。一旦连接建立&#xff0c;双方就可以通过输入输出流进行数据交换. ServerSocket是…

视频工具EasyDarwin生成RTMP给WVP拉流列表

效果 运行 登录 http://127.0.0.1:10086/ admin/admin 创建RTMP服务

微型导轨在光学仪器中的应用!

微型导轨在光学仪器中扮演着至关重要的角色&#xff0c;以其高精度、高稳定性的特点&#xff0c;提供稳定的光学路径和精确的光学元件位置。接下来&#xff0c;我们一起来看看微型导轨在光学仪器中的应用实例&#xff01; 1、显微镜&#xff1a;在显微镜中&#xff0c;微型导轨…

鹏哥C语言自定义笔记重点(67-)

67. 68. 69. 70. 71.结构体内容 72.理解结构体的字节数 73. #pragma once //头文件中使用&#xff0c;功能是:防止头文件被多次引用 74.结构体传参 结论:结构体传参时&#xff0c;要传结构体地址。 75.位段 76.static是只能在该文件中看到&#xff0c;其他地方看不到 77.…

【6大设计原则】迪米特法则:解密软件设计中的“最少知识原则”

引言 在软件设计中&#xff0c;设计原则是指导我们构建高质量、可维护系统的基石。迪米特法则&#xff08;Law of Demeter&#xff0c;LoD&#xff09;&#xff0c;也被称为“最少知识原则”&#xff0c;是六大设计原则之一。它强调对象之间的松耦合&#xff0c;确保系统的各个…

8. Transforms的使用(三)-- Resize

Transforms的使用(三) 1. 为什么要使用Resize 在模型的训练过程中往往需要图片数据的维度相同,才能适应深度学习模型中的相关神经网络结构,这时候就需要使用Resize保证所有的图片保持相同的尺寸2. 使用Resize调整图片的尺寸 在pytorch2.3的版本上,Resize()支持对Tensor类…

1405 问题 E: 世界杯

废话 这个题&#xff0c;我估计 22 22 22 年的时候写过一次&#xff0c;当时应该是搞明白了&#xff0c;现在重新写还是不会写&#xff0c;有点无奈 题目 问题 E: 世界杯&#xff1a;现在的 OJ 把题目加到一个活动里面去之后&#xff0c;感觉之后这个链接就访问不了了。题目…

CSS—4

1.定位 1.相对定位 2.绝对定位 3.固定定位 4.粘性定位 5.定位的特殊应用 2.布局-版心 3.布局-常用布局名词 4.布局-重置默认样式

321. 拼接最大数

1. 题目 321. 拼接最大数 2. 解题思路 题目精简一下&#xff1a; 给你两个数组&#xff0c;从每个数组选取N个元素&#xff08;需要保持相对顺序&#xff0c;比如从数组[4,8,2]选取两个元素&#xff0c;选取出来后必须保持顺序&#xff0c;比如选4和2&#xff0c;那么组成新…

对操作系统(OS)管理和进程的理解

文章目录 从冯诺依曼体系入手来了解计算机硬件部分操作系统操作系统的概念设计操作系统&#xff08;OS&#xff09;的目的对下&#xff08;硬件&#xff09;OS的管理对上如何理解系统调用 进程 在计算机系统中&#xff0c;硬件、操作系统和进程是三个至关重要的概念。它们相互协…

C# 反射之动态生成dll/exe

这个可能应该属于反射的高级使用范围了&#xff0c;平常在项目中使用的人估计也不是很多。由于使用反射的话会降低性能&#xff0c;比如之前用到的GetValue、SetValue等之类&#xff0c;但是使用这种方式会大大提高效率&#xff0c;在这里我只想说&#xff0c;都直接写IL指令了…