异步javaScript
在本文中,我们将解释什么是异步编程,为什么我们需要它,并简要讨论 JavaScript 历史上异步函数是怎样被实现的。
预备知识: | 基本的计算机素养,以及对 JavaScript 基础知识的一定了解,包括函数和事件处理程序。 |
---|---|
目标: | 熟悉异步 JavaScript 的概念,了解它与同步 JavaScript 的不同,以及我们需要它的原因。 |
异步编程技术使你的程序可以在执行一个可能长期运行的任务的同时继续对其他事件做出反应而不必等待任务完成。与此同时,你的程序也将在任务完成后显示结果。
浏览器提供的许多功能(尤其是最有趣的那一部分)可能需要很长的时间来完成,因此需要异步完成,例如:
- 使用 fetch() 发起 HTTP 请求
- 使用 getUserMedia() 访问用户的摄像头和麦克风
- 使用 showOpenFilePicker() 请求用户选择文件以供访问
因此,即使你可能不需要经常实现自己的异步函数,你也很可能需要正确使用它们。
在这篇文章中,我们将从同步函数长时间运行时存在的问题开始,并以此进一步认识异步编程的必要性。
同步编程
观察下面的代码:
const name = "Miriam";
const greeting = `Hello, my name is ${name}!`;
console.log(greeting);
// "Hello, my name is Miriam!"
这段代码:
- 声明了一个叫做
name
的字符串常量 - 声明了另一个叫做
greeting
的字符串常量(并使用了name
常量的值) - 将
greeting
常量输出到 JavaScript 控制台中。
我们应该注意的是,实际上浏览器是按照我们书写代码的顺序一行一行地执行程序的。浏览器会等待代码的解析和工作,在上一行完成后才会执行下一行。这样做是很有必要的,因为每一行新的代码都是建立在前面代码的基础之上的。
这也使得它成为一个同步程序。
事实上,调用函数的时候也是同步的,就像这样:
function makeGreeting(name) {return `Hello, my name is ${name}!`;
}
const name = "Miriam";
const greeting = makeGreeting(name);
console.log(greeting);
// "Hello, my name is Miriam!"
在这里 makeGreeting()
就是一个同步函数,因为在函数返回之前,调用者必须等待函数完成其工作。
一个耗时的同步函数
如果同步函数需要很长的时间怎么办?
当用户点击“生成素数”按钮时,这个程序将使用一种非常低效的算法生成一些大素数。你可以控制要生成的素数数量,这也会影响操作需要的时间。
<label for="quota">素数个数:</label>
<input type="text" id="quota" name="quota" value="1000000" /><button id="generate">生成素数</button>
<button id="reload">重载</button><div id="output"></div>
function generatePrimes(quota) {function isPrime(n) {for (let c = 2; c <= Math.sqrt(n); ++c) {if (n % c === 0) {return false;}}return true;}const primes = [];const maximum = 1000000;while (primes.length < quota) {const candidate = Math.floor(Math.random() * (maximum + 1));if (isPrime(candidate)) {primes.push(candidate);}}return primes;
}
document.querySelector("#generate").addEventListener("click", () => {const quota = document.querySelector("#quota").value;const primes = generatePrimes(quota);document.querySelector("#output",).textContent = `完成!已生成素数${quota}个。`;
});
document.querySelector("#reload").addEventListener("click", () => {document.location.reload();
});
试着点击“生成素数”按钮。在程序显示“完成!”信息之前可能需要几秒钟(取决于你的电脑性能)。
耗时同步函数的问题
接下来的示例和上一个一样,不过我们增加了一个文本框供你输入。这一次,试着点击“生成素数”,然后在文本框中输入。
你会发现,当我们的 generatePrimes()
函数运行时,我们的程序完全没有反应:用户不能输入任何东西,也不能点击任何东西,或做任何其他事情。
这就是耗时的同步函数的基本问题。在这里我们想要的是一种方法,以让我们的程序可以:
- 通过调用一个函数来启动一个长期运行的操作
- 让函数开始操作并立即返回,这样我们的程序就可以保持对其他事件做出反应的能力
- 当操作最终完成时,通知我们操作的结果。
这就是异步函数为我们提供的能力,本模块的其余部分将解释它们是如何在 JavaScript 中实现的。
事件处理程序
我们刚才看到的对异步函数的描述可能会让你想起事件处理程序,这么想是对的。事件处理程序实际上就是异步编程的一种形式:你提供的函数(事件处理程序)将在事件发生时被调用(而不是立即被调用)。如果“事件”是“异步操作已经完成”,那么你就可以看到事件如何被用来通知调用者异步函数调用的结果的。
一些早期的异步 API 正是以这种方式来使用事件的。XMLHttpRequest API 可以让你用 JavaScript 向远程服务器发起 HTTP 请求。由于这样的操作可能需要很长的时间,所以它被设计成异步 API,你可以通过给 XMLHttpRequest
对象附加事件监听器来让程序在请求进展和最终完成时获得通知。
下面的例子展示了这样的操作。点击“点击发起请求”按钮来发送一个请求。我们将创建一个新的 XMLHttpRequest 并监听它的 loadend 事件。而我们的事件处理程序则会在控制台中输出一个“完成!”的消息和请求的状态代码。
我们在添加了事件监听器后发送请求。注意,在这之后,我们仍然可以在控制台中输出“请求已发起”,也就是说,我们的程序可以在请求进行的同时继续运行,而我们的事件处理程序将在请求完成时被调用。
<button id="xhr">点击发起请求</button>
<button id="reload">重载</button><pre readonly class="event-log"></pre>
const log = document.querySelector(".event-log");
document.querySelector("#xhr").addEventListener("click", () => {log.textContent = "";const xhr = new XMLHttpRequest();xhr.addEventListener("loadend", () => {log.textContent = `${log.textContent}完成!状态码:${xhr.status}`;});xhr.open("GET","https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json",);xhr.send();log.textContent = `${log.textContent}请求已发起\n`;
});
document.querySelector("#reload").addEventListener("click", () => {log.textContent = "";document.location.reload();
});
这就像我们在以前的模块中遇到的事件处理程序,只是这次的事件不是像点击按钮那样的用户行为,而是某个对象的状态变化。
回调
事件处理程序是一种特殊类型的回调函数。而回调函数则是一个被传递到另一个函数中的会在适当的时候被调用的函数。正如我们刚刚所看到的:回调函数曾经是 JavaScript 中实现异步函数的主要方式。
然而,当回调函数本身需要调用其他同样接受回调函数的函数时,基于回调的代码会变得难以理解。当你需要执行一些分解成一系列异步函数的操作时,这将变得十分常见。例如下面这种情况:
function doStep1(init) {return init + 1;
}
function doStep2(init) {return init + 2;
}
function doStep3(init) {return init + 3;
}
function doOperation() {let result = 0;result = doStep1(result);result = doStep2(result);result = doStep3(result);console.log(`结果:${result}`);
}
doOperation();
现在我们有一个被分成三步的操作,每一步都依赖于上一步。在这个例子中,第一步给输入的数据加 1,第二步加 2,第三步加 3。从输入 0 开始,最终结果是 6(0+1+2+3)。作为同步代码,这很容易理解。但是如果我们用回调来实现这些步骤呢?
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()
函数,这就更难阅读和调试了。在一些地方这被称为“回调地狱”或“厄运金字塔”(因为缩进看起来像一个金字塔的侧面)。
面对这样的嵌套回调,处理错误也会变得非常困难:你必须在“金字塔”的每一级处理错误,而不是在最高一级一次完成错误处理。
由于以上这些原因,大多数现代异步 API 都不使用回调。事实上,JavaScript 中异步编程的基础是 Promise,这也是我们下一篇文章要讲述的主题。
相关文章:
异步javaScript
在本文中,我们将解释什么是异步编程,为什么我们需要它,并简要讨论 JavaScript 历史上异步函数是怎样被实现的。 预备知识:基本的计算机素养,以及对 JavaScript 基础知识的一定了解,包括函数和事件处理程序…...

看跨境电商世界区域分布,Live Market教你深入参与跨境创业
随着全球化发展带来互联网技术的进步和平台经济的触角伸向全球,跨境电商越来越成为全球贸易的重要组成部分。根据国际数据公司(IDC)的最新数据显示,全球前五大跨境电商平台分别是亚马逊、阿里巴巴、eBay、Wish和京东全球购。这五家…...

python中的装饰器的真正含义和用法
闭包: 闭包是python中的一个很实用的写法,可以使得用户在函数中调用该函数外的函数的变量,使得该变量常驻于内存中。 闭包函数: 输入是函数,输出也是一个函数。 装饰器的写法是python闭包的语法糖。 面试中经常面…...

opencv基础-38 形态学操作-闭运算(先膨胀,后腐蚀)cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
闭运算是先膨胀、后腐蚀的运算,它有助于关闭前景物体内部的小孔,或去除物体上的小黑点,还可以将不同的前景图像进行连接。 例如,在图 8-17 中,通过先膨胀后腐蚀的闭运算去除了原始图像内部的小孔(内部闭合的…...

RocketMQ生产者和消费者都开启Message Trace后,Consume Message Trace没有消费轨迹
一、依赖 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.3</version> </dependency>二、场景 1、生产者和消费者所属同一个程序 2、生产者开启消…...
JDV背后的技术-助力618 | 京东云技术团队
一、项目介绍 JDV(可视化大屏)是京东内部搭建可视化大屏的数据工具平台,内置10种模版特效,40种风格各异的图表、导航等组件。与集团其他数据工具打通,支持一站式、自助化、拖拽式搭建大屏,实现数据切换、联…...

0基础学习VR全景平台篇 第78篇:全景相机-拍摄VR全景
新手入门圆周率科技,成立于2012年,是中国最早投身嵌入式全景算法研发的团队之一,亦是全球市场占有率最大的全景算法供应商。相继推出一体化智能屏、支持一键高清全景直播的智慧全景相机--Pilot Era和Pilot One,为用户带来实时畅享…...

Spring MVC简介与概述
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
java基础复习(第六日)
java基础复习(五) 1.是否了解类似 RabbitMQ.kalka 之类的队列服务? 请简述队列取务中的常见要素和使用场景? 了解,队列服务是一种应用间的通信方式,可以实现异步处理、应用解耦、流量削峰和消息通信等功能 队列服务的常见要素:…...

商用服务机器人公司【Richtech Robotics】申请纳斯达克IPO上市
来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,总部位于美国内华达州拉斯维加斯由华人领导的商用服务机器人公司【Richtech Robotics】近期已向美国证券交易委员会(SEC)提交招股书,申请在纳斯达克IPO上市&am…...
关于nn.Embedding如何使用预定义词表
直接使用,则是没有pretrained的词表。 若要使用预定义词表,则可以用 pretrained_weight np.array(pretrained_weight) embeds.weight.data.copy_(torch.from_numpy(pretrained_weight))参考: https://wmathor.com/index.php/archives/1435/…...

怎么设置文件夹密码?文件夹密码设置方法合集
为文件夹设置密码可以有效地保护文件夹的数据安全,那么该怎么设置文件夹密码呢?下面我们来一起了解一下。 文件夹保护3000 想要简单快捷的为文件夹设置密码,那么,文件夹保护3000就是最佳的选择。它提供了3种文件夹保护方式&#…...

PEMFC氢氧质子交换燃料电池MATLAB仿真模型
氢氧燃料电池静态模型: (1)热力学电动势En (2)活化极化过电势Vact 活化损失主要是因为电极表面的反应速度过慢,在驱动电子传递到或者传送出电极的化学反应时,部分电压会被消耗。 (…...
创建PVC注意事项
On the one hand 创建永久卷(Persistent Volume Claim,PVC)时需要考虑以下几个因素: 存储类别(Storage Class):选择适合需求的存储类别是创建 PVC 的第一步。存储类别定义了永久卷的属性&…...

Sencha Ext.NET Crack 快速应用程序的正确工具集
Sencha Ext.NET Crack 快速应用程序的正确工具集 Sencha Ext.NET是一个高级的ASP.NET核心组件框架,它包含了强大的跨浏览器Sencha Ext JS库。通过140多个预构建和专业测试的UI组件实现企业级性能和生产效率。Sencha Ext.NET使用尖端的Web技术创建功能强大的Web应用程…...
transformer学习
transformer 李宏毅老师的课:https://www.youtube.com/watch?vugWDIIOHtPA&listPLJV_el3uVTsOK_ZK5L0Iv_EQoL1JefRL4&index60 知乎上的文章:Transformer模型详解(图解最完整版) 主要参考tensorflow的官方文档…...

基于LNMP架构搭建Discuz论坛
LNMP: L---->linux系统,操作系统。 N----->nginx网站服务(前端),提供前端的静态页面服务。同时具有代理、转发的作用。(转发就是转发后端请求,转发PHP),nginx没有处理动态资源的功能,他有…...
乐鑫科技2021笔试题
笔试时间:2020.09.09,10:00-11:30 岗位:嵌入式软件工程师 题型:单选题20道,40分。编程题2道,60分。 单选题 1、算术右移指令执行的操作是?符号位会变化吗?…...

VL 模型 Open-Set Domain Adaptation with Visual-Language Foundation Models 论文阅读笔记
Open-Set Domain Adaptation with Visual-Language Foundation Models 论文阅读笔记 一、Abstract 写在前面 又是一周周末,在家的时间感觉过得很快呀。今天没得时间写博客,留下个标题,明天搞完。 论文地址:Open-Set Domain Adapta…...

在IDEA同一个窗口中同时打开多个独立项目
文章说明 本文主要说明如何在Intellij Idea中同时打开多个独立的Maven项目。 我在使用idea的时候,由于自己负责了很多项目,经常要在不通的代码之间切换来切换去。然后搜索代码的时候也只能搜到当前打开的这个项目。因为这个原因,一些小项目…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...