第十一章——期约与异步函数
ECMAScript 6及之后的几个版本逐步加大了对异步编程机制的支持,提供了令人眼前一亮的新特性。ECMAScript 6新增了正式的Promise(期约)引用类型,支持优雅地定义和组织异步逻辑。接下来几个版本增加了使用async和await关键字定义异步函数的机制。
11.1 异步编程
同步行为和异步行为的对立统一是计算机科学的一个基本概念。特别是在JavaScript这种单线程事件循环模型中,同步操作与异步操作更是代码所要依赖的核心机制。异步行为是为了优化因计算量大而时间长的操作。如果在等待其他操作完成的同时,即使运行其他指令,系统也能保持稳定,那么这样做就是务实的。
重要的是,异步操作并不一定计算量大或要等很长时间。只要你不想为等待某个异步操作而阻塞线程执行,那么任何时候都可以使用。
11.1.1 同步与异步
同步行为对应内存中顺序执行的处理器指令。每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息。这样的执行流程容易分析程序在执行到代码任意位置时的状态(比如变量的值)。
同步操作的例子可以是执行一次简单的数学计算:
let x = 3;
x = x + 4;
在程序执行的每一步,都可以推断出程序的状态。这是因为后面的指令总是在前面的指令完成后才会执行。等到最后一条指定执行完毕,存储在x的值就立即可以使用。
这两行JavaScript代码对应的低级指令(从JavaScript到x86)并不难想象。首先,操作系统会在栈内存上分配一个存储浮点数值的空间,然后针对这个值做一次数学计算,再把计算结果写回之前分配的内存中。所有这些指令都是在单个线程中按顺序执行的。在低级指令的层面,有充足的工具可以确定系统状态。
相对地,异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须要等)。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待。
异步操作的例子可以是在定时回调中执行一次简单的数学计算:
let x = 3;
setTimeout(() => x = x + 4, 1000);
这段程序最终与同步代码执行的任务一样,都是把两个数加在一起,但这一次执行线程不知道x值何时会改变,因为这取决于回调何时从消息队列出列并执行。
异步代码不容易推断。虽然这个例子对应的低级代码最终跟前面的例子没什么区别,但第二个指令块(加操作及赋值操作)是由系统计时器触发的,这会生成一个入队执行的中断。到底什么时候会触发这个中断,这对JavaScript运行时来说是一个黑盒,因此实际上无法预知(尽管可以保证这发生在当前线程的同步代码执行之后,否则回调都没有机会出列被执行)。无论如何,在排定回调以后基本没办法知道系统状态何时变化。
为了让后续代码能够使用x,异步执行的函数需要在更新x的值以后通知其他代码。如果程序不需要这个值,那么就只管继续执行,不必等待这个结果了。
设计一个能够知道x什么时候可以读取的系统是非常难的。JavaScript在实现这样一个系统的过程中也经历了几次迭代。
11.1.2 以往的异步编程模式
异步行为是JavaScript的基础,但以前的实现不理想。在早期的JavaScript中,只支持定义回调函数来表明异步操作完成。串联多个异步操作是一个常见的问题,通常需要深度嵌套的回调函数(俗称“回调地狱”)来解决。
假设有以下异步函数,使用了setTimeout在一秒钟之后执行某些操作:
function double(value) {setTimeout(() => setTimeout(console.log, 0, value * 2), 1000);
}double(3);
// 6(大约1000毫秒之后)
这里的代码没什么神秘的,但关键是理解为什么说它是一个异步函数。setTimeout可以定义一个在指定时间之后会被调度执行的回调函数。对这个例子而言,1000毫秒之后,JavaScript运行时会把回调函数推到自己的消息队列上去等待执行。推到队列之后,回调什么时候出列被执行对JavaScript代码就完全不可见了。还有一点,double()函数在setTimeout成功调度异步操作之后会立即退出。
1、异步返回值
假设setTimeout操作会返回一个有用的值。有什么好办法把这个值传给需要它的地方?广泛接受的一个策略是给异步操作提供一个回调,这个回调中包含要使用异步返回值的代码(作为回调的参数)。
function double(value, callback) {setTimeout(() => callback(value * 2), 1000);
}double(3, (x) => console.log(`I was given: ${x}`));
// I was given: 6(大约1000毫秒之后)
这里的setTimeout调用告诉JavaScript运行时在1000毫秒之后把一个函数推到消息队列上。这个函数会由运行时负责异步调度执行。而位于函数闭包中的回调及其参数在异步执行时仍然是可用的。
2、失败处理
异步操作的失败处理在回调模型中也要考虑,因此自然就出现了成功回调和失败回调:
function double(value, success, failure) {setTimeout(() => {try {if (typeof value !== 'number') {throw 'Must provide number as first argument';}success(2 * value);} catch (e) {failure(e);}}, 1000);
}const successCallback = (x) => console.log(`Success: ${x}`);
const failureCallback = (e) => console.log(`Failure: ${e}`);double(3, successCallback, failureCallback);
double('b', successCallback, failureCallback);// Success: 6(大约1000毫秒之后)
// Failure: Must provide number as first argument(大约1000毫秒之后)
这种模式已经不可取了,因为必须在初始化异步操作时定义回调。异步函数的返回值只在短时间内存在,只有预备好将这个短时间内存在的值作为参数的回调才能接收到它。
3、嵌套异步回调
如果异步返值又依赖另一个异步返回值,那么回调的情况还会进一步变复杂。在实际的代码中,这就要求嵌套回调:
function double(value, success, failure) {setTimeout(() => {try {if (typeof value !== 'number') {throw 'Must provide number as first argument';}success(2 * value);} catch (e) {failure(e);}}, 1000);
}const successCallback = (x) => {double(x, (y) => console.log(`Success: ${y}`));
};
const failureCallback = (e) => console.log(`Failure: ${e}`);double(3, successCallback, failureCallback);// Success: 12(大约1000毫秒之后)
显然,随着代码越来越复杂,回调策略是不具有扩展性的。“回调地狱”这个称呼可谓名至实归。嵌套回调的代码维护起来就是噩梦。
11.2 期约
期约是对尚不存在结果的一个替身。期约(promise)这个名字最早是由Daniel Friedman和David Wise在他们于1976年发表的论文“The Impact of Applicative Programming on Multiprocessing”中提出来的。但直到十几年以后,Barbara Liskov和Liuba Shrira在1988年发表了论文“Promises—Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed
Systems”,这个概念才真正确立下来。同一时期的计算机科学家还使用了“终局”(eventual)、“期许”(future)、“延迟”(delay)和“迟付”(deferred)等术语指代同样的概念。所有这些概念描述的都是一种异步程序执行的机制。
11.2.1 Promises/A+规范
早期的期约机制在jQuery和Dojo中是以Deferred API的形式出现的。到了2010年,CommonJS项目实现的Promises/A规范日益流行起来。Q和Bluebird等第三方JavaScript期约库也越来越得到社区认可,虽然这些库的实现多少都有些不同。为弥合现有实现之间的差异,2012年
Promises/A+组织分叉(fork)了CommonJS的Promises/A建议,并以相同的名字制定了Promises/A+规范。这个规范最终成为了ECMAScript 6规范实现的范本。
ECMAScript 6增加了对Promises/A+规范的完善支持,即Promise类型。一经推出,Promise就大受欢迎,成为了主导性的异步编程机制。所有现代浏览器都支持ES6期约,很多其他浏览器API(如fetch()和Battery Status API)也以期约为基础。
11.2.2 期约基础
ECMAScript 6新增的引用类型Promise,可以通过new操作符来实例化。创建新期约时需要传入执行器(executor)函数作为参数(后面马上会介绍),下面的例子使用了一个空函数对象来应付一下解释器:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
之所以说是应付解释器,是因为如果不提供执行器函数,就会抛出SyntaxError。
1、期约状态机
在把一个期约实例传给console.log()时,控制台输出(可能因浏览器不同而略有差异)表明该实例处于待定(pending)状态。如前所述,期约是一个有状态的对象,可能处于如下3种状态之一:
- 待定(pending)
- 兑现(fulfilled,有时候也称为“解决”,resolved)
- 拒绝(rejected)
待定(pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,期约的状态就不再改变。而且,也不能保证期约必然会脱离待定状态。因此,组织合理的代码无论期约解决(resolve)还是拒绝(reject),甚至永远处于待定
(pending)状态,都应该具有恰当的行为。
重要的是,期约的状态是私有的,不能直接通过JavaScript检测到。这主要是为了避免根据读取到的期约状态,以同步方式处理期约对象。另外,期约的状态也不能被外部JavaScript代码修改。这与不能读取该状态的原因是一样的:期约故意将异步行为封装起来,从而隔离外部的同步代码。
2、解决值、拒绝理由及期约用例
期约主要有两大用途。首先是抽象地表示一个异步操作。期约的状态代表期约是否完成。“待定”表示尚未开始或者正在执行中。“兑现”表示已经成功完成,而“拒绝”则表示没有成功完成。
某些情况下,这个状态机就是期约可以提供的最有用的信息。知道一段异步代码已经完成,对于其他代码而言已经足够了。比如,假设期约要向服务器发送一个HTTP请求。请求返回200~299范围内的状态码就足以让期约的状态变为“兑现”。类似地,如果请求返回的状态码不在200~299这个范围内,那么就会把期约状态切换为“拒绝”。
在另外一些情况下,期约封装的异步操作会实际生成某个值,而程序期待期约状态改变时可以访问这个值。相应地,如果期约被拒绝,程序就会期待期约状态改变时可以拿到拒绝的理由。比如,假设期约向服务器发送一个HTTP请求并预定会返回一个JSON。如果请求返回范围在200~299的状态码,则足以让期约的状态变为兑现。此时期约内部就可以收到一个JSON字符串。类似地,如果请求返回的状态码不在200~299这个范围内,那么就会把期约状态切换为拒绝。此时拒绝的理由可能是一个Error对象,包含着HTTP状态码及相关错误消息。
为了支持这两种用例,每个期约只要状态切换为兑现,就会有一个私有的内部值(value)。类似地,每个期约只要状态切换为拒绝,就会有一个私有的内部理由(reason)。无论是值还是理由,都是包含原始值或对象的不可修改的引用。二者都是可选的,而且默认值为undefined。在期约到达某个落定状态时执行的异步代码始终会收到这个值或理由。
3、通过执行函数控制期约状态
由于期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。其中,控制期约状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为resolve()和reject()。调用resolve()会把状态切换为兑现,调用reject()会把状态切换为拒绝。另外,调用reject()也会抛出错误(后面会讨论这个错误)。
let p1 = new Promise((resolve, reject) => resolve());
setTimeout(console.log, 0, p1); // Promise <resolved>let p2 = new Promise((resolve, reject) => reject());
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught error (in promise)
在前面的例子中,并没有什么异步操作,因为在初始化期约时,执行器函数已经改变了每个期约的状态。这里的关键在于,执行器函数是同步执行的。这是因为执行器函数是期约的初始化程序。通过下面的例子可以看出上面代码的执行顺序:
new Promise(() => setTimeout(console.log, 0, 'executor'));
setTimeout(console.log, 0, 'promise initialized');// executor
// promise initialized
添加setTimeout可以推迟切换状态:
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));// 在console.log打印期约实例的时候,还不会执行超时回调(即resolve())
setTimeout(console.log, 0, p); // Promise <pending>
无论resolve()和reject()中的哪个被调用,状态转换都不可撤销了。于是继续修改状态会静默失败,如下所示:
let p = new Promise((resolve, reject) => {resolve();reject(); // 没有效果
});setTimeout(console.log, 0, p); // Promise <resolved>
为避免期约卡在待定状态,可以添加一个定时退出功能。比如,可以通过setTimeout设置一个10秒钟后无论如何都会拒绝期约的回调:
let p = new Promise((resolve, reject) => {setTimeout(reject, 10000); // 10秒后调用reject()// 执行函数的逻辑
});setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 11000, p); // 11秒后再检查状态// (After 10 seconds) Uncaught error
// (After 11 seconds) Promise <rejected>
因为期约的状态只能改变一次,所以这里的超时拒绝逻辑中可以放心地设置让期约处于待定状态的最长时间。如果执行器中的代码在超时之前已经解决或拒绝,那么超时回调再尝试拒绝也会静默失败。
4.Promise.resolve()
期约并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。通过调用Promise.resolve()静态方法,可以实例化一个解决的期约。下面两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
这个解决的期约的值对应着传给Promise.resolve()的第一个参数。使用这个静态方法,实际上可以把任何值都转换为一个期约:
setTimeout(console.log, 0, Promise.resolve());
// Promise <resolved>: undefinedsetTimeout(console.log, 0, Promise.resolve(3));
// Promise <resolved>: 3// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4
对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此,Promise.resolve()可以说是一个幂等方法,如下所示:
let p = Promise.resolve(7);setTimeout(console.log, 0, p === Promise.resolve(p));
// truesetTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true
这个幂等性会保留传入期约的状态:
let p = new Promise(() => {});setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>setTimeout(console.log, 0, p === Promise.resolve(p)); // true
注意,这个静态方法能够包装任何非期约值,包括错误对象,并将其转换为解决的期约。因此,也可能导致不符合预期的行为:
let p = Promise.resolve(new Error('foo'));setTimeout(console.log, 0, p);
// Promise <resolved>: Error: foo
5.Promise.reject()
与Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过try/catch捕获,而只能通过拒绝处理程序捕获)。下面的两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
这个拒绝的期约的理由就是传给Promise.reject()的第一个参数。这个参数也会传给后续的拒绝处理程序:
let p = Promise.reject(3);
setTimeout(console.log, 0, p); // Promise <rejected>: 3p.then(null, (e) => setTimeout(console.log, 0, e)); // 3
关键在于,Promise.reject()并没有照搬Promise.resolve()的幂等逻辑。如果给它传一个期约对象,则这个期约会成为它返回的拒绝期约的理由:
setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
// Promise <rejected>: Promise <resolved>
6、同步/异步执行的二元性
Promise的设计很大程度上会导致一种完全不同于JavaScript的计算模式。下面的例子完美地展示了这一点,其中包含了两种模式下抛出错误的情形:
try {throw new Error('foo');
} catch(e) {console.log(e); // Error: foo
}try {Promise.reject(new Error('bar'));
} catch(e) {console.log(e);
}// Uncaught (in promise) Error: bar
第一个try/catch抛出并捕获了错误,第二个try/catch抛出错误却没有捕获到。乍一看这可能有点违反直觉,因为代码中确实是同步创建了一个拒绝的期约实例,而这个实例也抛出了包含拒绝理由的错误。这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这里就可以看出期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式的媒介。
在前面的例子中,拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的。因此,try/catch块并不能捕获该错误。代码一旦开始以异步模式执行,则唯一与之交互的方式就是使用异步结构——更具体地说,就是期约的方法。
11.2.3 期约的实例方法
期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或者添加只有期约进入终止状态时才会执行的代码。
未完待续。。。
相关文章:

第十一章——期约与异步函数
ECMAScript 6及之后的几个版本逐步加大了对异步编程机制的支持,提供了令人眼前一亮的新特性。ECMAScript 6新增了正式的Promise(期约)引用类型,支持优雅地定义和组织异步逻辑。接下来几个版本增加了使用async和await关键字定义异步…...

工具方法合集-utils.js
通用 import get from lodash.get import cloneDeep from lodash.clonedeep // 深度clone export function deepClone(obj) {return obj ? cloneDeep(obj) : obj } export function lodashGet(obj, key, defaultValue = ) {//这个 defaultValue 不能给默认 值 会报错;retur…...

安卓11-设置HDMI分辨率流程
安卓11中从设置-显示设置hdmi分辨率流程:framework层通过jni控制底层驱动实现,标准驱动模型 packages\apps\Settings\src\com\android\settings\display\HdmiSettings.javaprivate void updateResolution(final ITEM_CONTROL control, final int index) {showWaitin…...

Vue3+vite搭建基础架构(11)--- 菜单栏功能和Tab页功能实现
Vue3vite搭建基础架构(11)--- 菜单栏功能和Tab页功能实现 说明删除项目中不需要的文件userStore全局属性代码菜单栏代码Tab页代码解决浏览器输入地址时不会打开tab页问题和切换tab页时参数丢失问题 说明 这里记录下自己在Vue3vite的项目实现菜单栏功能和…...

餐饮神秘顾客公司:关于餐饮行业神秘顾客调查注意事项
在餐饮业,顾客体验往往决定品牌的成败。为深入了解顾客需求和感受,许多餐饮企业引入“神秘顾客”调查。然而,此调查并非简单走过场,其中细节和注意事项颇多。餐饮行业神秘顾客调查需注意以下几点: 1. 专业培训&#x…...

概率密度函数(PDF)与神经网络中的激活函数
原创:项道德(daode3056,daode1212) 在量子力学中,许多现象都是统计的结果,基本上用的是正态分布,然而,从本质上思考,应该还存在低阶的分布,标准的正态分布是它的极限,这样一来,或许在…...

.netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
1、SqlSugarCore 相关 1.1 主项目添加数据,否则会报数据库连接错误: <InvariantGlobalization>false</InvariantGlobalization> <PropertyGroup><TargetFramework>net8.0</TargetFramework><Nullable>enable</…...

算法打卡day2|数组篇|Leetcode 977.有序数组的平方、 209.长度最小的子数组、59.螺旋矩阵II
算法题 Leetcode 977.有序数组的平方 题目链接: 977.有序数组的平方 大佬视频讲解:977.有序数组的平方 个人思路 第一时间就只想到暴力解法,双重循环一个循环比较一个循环赋值;但这样可能会超时,所以还能用双指针࿰…...

Hive【内部表、外部表、临时表、分区表、分桶表】【总结】
目录 Hive的物种表结构特性 一、内部表 建表 使用场景 二、外部表 建表:关键词【EXTERNAL】 场景: 外部表与内部表可互相转换 三、临时表 建表 临时表横向对比编辑 四、分区表 建表:关键字【PARTITIONED BY】 场景: 五、分桶表 …...

随手写的小程序2 一个nc能控制的程序
小程序源代码 下载: https://download.csdn.net/download/nn_84/88846445?spm1001.2014.3001.5501 请下载 Qt 5.12.12 server.pro : QT gui networkCONFIG c11 console CONFIG - app_bundle# You can make your code fail to compile if it uses deprecated APIs. # In o…...

Android中通过属性动画实现文字轮播效果
前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。 👉点击跳转到教程 一、创建一个自定义ProvinceView类,具体代码如下 /*** Author: ly* Date: 2024/2/22* D…...

最长的回文串
开始想的简单了,确实没想到奇数字母删去一个后也能用 解法: 桶排序 #include<iostream> #include<vector> #include<algorithm> using namespace std; #define endl \n #define int long long signed main() {int t;cin >> t…...

2023 H1 中国边缘公有云服务市场 Top2,百度智能云加速推动分布式云智能化升级
近期,IDC 发布了《中国边缘云市场跟踪研究 2023 H1》。报告显示,2023 上半年,中国边缘公有云服务市场规模 24.3 亿元,同比增速达到 41.8%。 其中,百度智能云以 15.7% 的市场份额位列中国边缘公有云服务市场第二&#…...

Emlog博客网站快速搭建并结合内网穿透实现远程访问本地站点
文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道(云端设置)2.3.Cpolar稳定隧道(本地设置) 3. 公网访问测试总结 前言 博客作为使…...

力扣经典题目解析--旋转图像(字节二面)
题目 原题地址: . - 力扣(LeetCode) 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1࿱…...

【ARMv8M Cortex-M33 系列 8.1 -- RT-Thread 堆内存 检查命令 free 实现及介绍】
文章目录 RT-Thread 堆内存 检查命令 free 实现及介绍rt_memory_info 函数验证 RT-Thread 堆内存 检查命令 free 实现及介绍 在RT-Thread系统中,通常可以通过rt_memory_info函数获取当前的堆内存使用信息,然后你可以包装这个函数来显示剩余的堆空间。rt…...

milvus Delete API流程源码分析
Delete API执行流程源码解析 milvus版本:v2.3.2 整体架构: Delete 的数据流向: 1.客户端sdk发出Delete API请求。 from pymilvus import (connections,Collection, )print("start connecting to Milvus") connections.connect("default", host"192…...

CentOS使用Docker搭建Halo网站并实现无公网ip远程访问
🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默&…...

【JVM】垃圾回收算法
📝个人主页:五敷有你 🔥系列专栏:JVM ⛺️稳中求进,晒太阳 垃圾回收算法 Java是如何实现垃圾回收的呢?简单来说,垃圾回收就做两件事 找到内存中存活的对象释放不在存活对象的内存&…...

如何和将原始request的Header中的值传递给openfeign请求的Header? 以及又如何获取openfeign请求中Header中的值
如何和将原始request的Header中的值传递给openfeign请求的Header? 以及又如何获取openfeign请求中Header中的值 如何和将原始request的Header中的值传递给openfeign请求的Header参考 [https://www.jb51.net/article/282522.htm](https://www.jb51.net/article/28252…...

Flink 侧输出流(SideOutput)
🌸在平时大部分的 DataStream API 的算子的输出是单一输出,也就是某一种或者说某一类数据流,流向相同的地方。 🌸在处理不同的流中,除了 split 算子,可以将一条流分成多条流,这些流的数据类型也…...

C语言中关于#include的一些小知识
写代码的过程中,因为手误,重复包含了头文件 可以看到没有报错 如果是你自己编写的头文件,那么如果没加唯一包含标识的话,那么编译器会编译报错的。如果是系统自带的头文件,由于其每个头文件都加了特殊标识,…...

DSP芯片 机器码下载方法 【主要 “扯” 用Uniflash下载的方法】
还是以德州仪器的TMS320F28335芯片为 “栗子”,说说这事儿。 编制好的程序经过开发环境可以编译成扩展名为 .out 文件,这个文件就是DSP可以运行机器码,我们把这个文件下载到 DSP芯片中的程序区, 下载好了,这个芯片原…...

速盾网络:CDN用几天关了可以吗?安全吗?
在使用CDN(内容分发网络)时,有时候会考虑暂时关闭或暂停使用CDN服务的情况。但是,对于关闭CDN服务的时间长短以及安全性问题,很多人可能存在疑问。在本文中,我们将讨论CDN使用中关闭几天是否安全以及相关注…...

MR混合现实情景实训教学系统在高空作业课堂中的应用
高空作业是一项高风险的工作,对于从业者来说,不仅需要具备专业的技能,还需要有丰富的实践经验。然而,传统的课堂教学往往只能通过理论讲解和模拟训练来传授知识,无法提供真实的实践环境。而MR混合现实情景实训教学系统…...

Windows系统中定时执行python脚本
背景:本地Windows系统指定目录下会有文件的修改新增,这些变化的文件需要定时的被上传到git仓库中,这样不需要每次变更手动上传了。 首先编写一个检测文件夹下文件变化并且上传git仓库的python脚本(确保你已经在E:\edc_workspace\data_edc_et…...

HashMap 源码学习-jdk1.8
1、一些常量的定义 这里针对MIN_TREEIFY_CAPACITY 这个值进行解释一下。 java8里面,HashMap 的数据结构是数组 (链表或者红黑树),每个数组节点下可能会存在链表和红黑树之间的转换,当同一个索引下面的节点超过8个时…...

WebStorm 2023:让您更接近理想的开发环境 mac/win版
JetBrains WebStorm 2023激活版下载是一款强大而智能的Web开发工具,专为提高开发人员的生产力而设计。这款编辑器提供了许多先进的代码编辑功能,以及一系列实用的工具和插件,可帮助您更快地编写、调试和测试代码。 WebStorm 2023软件获取 We…...

java面试题:数字与字母的映射表
前言 好记性不如烂笔头。 问题: 现在有一个数字与字母的映射表,且有以下规则: 映射表: 数字 字母 3 A 7 B 9 C 规则: 1.碰到当前数字时,使用字母替换,例如,3-> A 2.碰到当前数…...

Jmeter教程-JMeter 环境安装及配置
Jmeter教程 JMeter 环境安装及配置 在使用 JMeter 之前,需要配置相应的环境,包括安装 JDK 和获取 JMeter ZIP 包。 安装JDK 1.JDK下载 示例环境为Windows11环境,读者应根据实际环境下载JDK的安装包。 JDK下载地址: Java21 下载 …...