当前位置: 首页 > news >正文

JS 异步 ( 二、Promise 的用法、手写模拟 Promise )


文章目录

  • 一、Promise 基础
    • Promise 作用
    • Promise 语法
    • Promise 内部状态值 和 链式调用
    • Promise 是否为异步执行
    • Promise 常用函数或属性
  • 二、模拟 Promise,加深理解

一、Promise 基础


Promise 作用


1. 回调地狱
想知道 Promise 的作用, 需要先了解一个概念叫回调地狱。回调地狱是指,回调函数间多层嵌套调用后,引发的可读性问题。

这样解释可能太抽象,下面举例演示一下。
需求:输出 a b c d e 五个字母,每个字母输出间隔为 500ms

(1) 使用内置函数引发的回调地狱

<!doctype html>
<script>// 回调地狱setTimeout(function () {console.log('a')setTimeout(function () {console.log('b')setTimeout(function () {console.log('c')setTimeout(function () {console.log('d')setTimeout(function () {console.log('e')console.log('输出结束')}, 500)}, 500)}, 500)}, 500)}, 500)
</script>

(2) 使用自定义函数引发的回调地狱

<script>// 定义函数,参数是回调函数let fn = function (word, callback) {setTimeout(function () {console.log(word)callback()}, 500)}// 回调地狱fn('a', function () {fn('b', function () {fn('c', function () {fn('d', function () {fn('e', function () {console.log('输出结束')})})})})})
</script>

2. Promise

通过上面的代码可以了解到,发生回调地狱后,代码的可读性和可维护性有多么差,Promise 用链式调用的方式很好的解决了回调地狱的问题,从而增强了代码的可读性。

上面输出 a b c d e 的需求,用 Promise 来实现后,明显清爽了许多:

<script>// 定义函数let promise = function (word) {return new Promise((resolve => {setTimeout(function () {console.log(word)resolve()}, 500)}))}// 链式调用promise('a').then(() => {return promise('b')}).then(() => {return promise('c')}).then(() => {return promise('d')}).then(() => {return promise('e')}).then(() => {console.log('输出结束')})
</script>

Promise 语法


常见的 Promise 语法结构如下:

<script>// 用一个随机数大于 5 就成功,否则就失败的例子, 来// 展示一下较完整且常见的 Promise 语法结构new Promise((resolve, reject) => {const random = Math.ceil(Math.random() * 10)if(random > 5){resolve(random)}else{reject(random)}}).then((data) => {console.log('随机数为:' + data + ',大于5,执行成功!')}).catch((data) => {console.log('随机数为:' + data + ',小于等于5,执行失败!')}).finally(() => {console.log('执行结束!')})
</script>

语法结构拆解:

1. 构造函数

Promise 的构造器需要一个回调函数作为参数(该参数后面简称 executor),executor 被回调时会被传入两个函数类型的参数,一般习惯称为 resolve 和 reject,当 executor 中调用这两个函数时,会将其加入到 <微队列> 中, 等主线程空闲后再从队列中取出执行,这两个函数的主要作用是执行收集回来的处理程序

<script>// executor 被回调时不需要使用 resolve 和 reject 函数new Promise(function () {})// executor 被回调时只需要使用 resolve 函数new Promise(function (resolve) {resolve()})// executor 被回调时需要使用 resolve 或 reject 函数new Promise(function (resolve, reject) {resolve()reject()})
</script>

2. then 方法

then 方法用来收集处理程序,其参数为两个可选的回调函数 (处理程序),第一个参数为 resolve 的处理程序,会被收集到 resolve 处理程序队列,第二个参数为 reject 的处理程序,会被收集到 reject 处理程序队列

<script>// 声明一个 Promise 对象let promise = new Promise(function (resolve, reject) {resolve('调用处理程序1')reject('调用处理程序2')})promise.then(() => {}) // resolve 处理程序不需要参数promise.then(data => {}) // resolve 处理程序需要参数promise.then(undefined, () => {}) // reject 处理程序不需要参数promise.then(undefined, message => {}) // reject 处理程序需要参数promise.then(() => {}, () => {}) // resolve 和 reject 处理程序都不需要参数promise.then(data => {}, message => {}) // resolve 和 reject 处理程序都需要参数
</script>

3. catch

catch 方法与 then 方法第二个参数的作用及原理是一样的,一般习惯将 resolve 的处理程序写在 then 方法的第一个参数中,把 reject 处理程序写在 catch 方法中,这样代码可读性更高,结构更清晰

<script>new Promise((resolve, reject) => {reject()}).then(() => console.log('resolve 处理程序执行')).catch(() => console.log('reject 处理程序执行'))
</script>

4. finally

finally 方法的参数也是回调函数,并且该回调函数没有参数,当 executor 中调用了 resolve、 reject 或 同步执行的代码发生异常时会执行该回调。

<script>let promise = new Promise((resolve, reject) => {resolve('数据')reject('错误信息')})promise.then((data) => console.log("resolve 执行: " + data), (message) => console.log("reject 执行: " + message))promise.finally(() => {console.log('finally 执行')})
</script>

上面代码等同于:

<script>let promise = new Promise((resolve, reject) => {resolve('数据')reject('错误信息')})promise.then((data) => {console.log("resolve 执行: " + data)console.log('finally 执行')}, (message) => {console.log("reject 执行: " + message)console.log('finally 执行')throw message})
</script>

Promise 内部状态值 和 链式调用

请添加图片描述

Promise 拥有三种状态:
1. pending:对象刚创建时的状态
2. fulfilled:executor 中调用 resolve 后的状态。
3. rejected:executor 中的同步代码出现异常或者调用 reject 后的状态

如上图所示,状态只能由 pending ➡ fulfilled 或 pending ➡ rejected,这也就表示 executor 中 resolve 和 reject 只能同时执行一个,不能 fulfilled ⬅➡ rejected 这样变化

链式调用:

先认识一下链式调用的代码,
注意下面这种不属于链式调用,这只是给同一个 Promise 对象添加多个 resolve 处理程序而已:

<script>let promise = new Promise(((resolve) => {resolve()}))promise.then(() => {console.log('执行成功,第一次');})promise.then(() => {console.log('执行成功,第二次');})promise.then(() => {console.log('执行成功,第三次');})
</script>

这才是真正意义上的链式调用:

<script>new Promise(((resolve) => {resolve()})).then(() => {console.log('执行成功,第一次');}).then(() => {console.log('执行成功,第二次');}).then(() => {console.log('执行成功,第三次');})
</script>

之所以能够实现链式调用,是因为 then 方法的返回值永远是一个 Promise 对象,该 Promise 的内部状态,与被执行的处理程序 ( resolve 处理程序或 reject 处理程序 ) 的返回值有关。

情况1:处理程序显式返回 Promise 对象时,then 方法返回的 Promise 对象的内部状态应与其一致:

<script>// resolve 处理程序显式返回状态为 fulfilled 的 Promise 对象, 所以 then 方法最终返回的 // Promise 对象的内部状态也是 fulfilled, 然后程序会链式的执行下个 then 方法的 resovle 处理程序Promise.resolve().then(() => {return Promise.resolve('可选参数')}).then((data) => console.log(data))// resolve 处理程序显式返回状态为 rejected 的 Promise 对象, 所以 then 方法最终返回的 // Promise 对象的内部状态也是 rejected, 然后程序会链式的执行下个 then 方法的 reject 处理程序Promise.resolve().then(() => {return Promise.reject('可选参数')}).catch((data) => console.log(data))
</script>

情况2:处理程序未显式返回 Promise 对象时,then 方法会返回一个状态为 fulfilled 的 Promise 对象:

<script>// 未显式返回任何数据Promise.resolve().then(() => {console.log('未显式返回任何数据')}).then(() => console.log('处理程序未显式返回 Promise 对象时,then 方法会返回一个状态为 fulfilled 的 Promise 对象'))// 显式返回非 Promise 对象时, 会把返回的数据当作参数传递给下一个处理程序Promise.resolve().then(() => {return '这个字符串会当作参数传给下一个处理程序'}).then((data) => console.log(data))
</script>

Promise 是否为异步执行

提到 Promise 经常有人跟异步扯上关系,那是因为 Promise 经常与异步操作一起使用 ( 计时器,Ajax ) 造成的错觉,其实 Promise 本身是同步执行的,只有调用 resolve 或 reject 时,resolve 或 reject 会被加入到 <微队列> 中,等到主线程空闲时,再从队列中取出,并执行对应的处理程序,但这并不影响 executor 中其他代码先执行

<script>console.log(1)let promise = new Promise(resolve => {console.log(2)resolve(3) // 此处 resolve 会被加入到 <微队列> 中,等到主线程空闲时,再从队列中取出,并执行对应的处理程序console.log(4)})console.log(5)promise.then(data => {console.log(data)})console.log(6)// 输出:// 1、2、4、5、6、3
</script>

Promise 常用函数或属性


1. Promise.all

判断任务是否全部为 fulfilled 状态,当全部为 fulfilled 时,会返回一个状态为 fulfilled 的 Promise 新对象, 并会收集所有任务的 resolve 处理程序的参数,将其封装成一个数组,传递给新的 Promise 的 resolve 处理程序,当有任意一个任务的状态为 rejected 时,会立即中断并返回一个状态为 rejected 的 Promise 新对象,并把失败任务的 reject 处理程序的参数,传递给新的 Promise 的 reject 处理程序。

简单总结就是,all 用来判断是否全部成功

任务全部为 fulfilled 状态:

<script>// 状态为 fulfilled 的 Promise 对象let promise1 = new Promise(resolve => {resolve('第一个 Promise');})// 状态为 fulfilled 的 Promise 对象let promise2 = new Promise(resolve => {resolve('第二个 Promise');})// 任务全部为 fulfilled 状态,会执行 Promise.all 返回的新 Promise 对象的 resolve 处理程序Promise.all([promise1, promise2]).then(data => {console.log(data)})
</script>

有任务为 rejected 状态:

<script>// 状态为 fulfilled 的 Promise 对象let promise1 = new Promise(resolve => {resolve('第一个 Promise');})// 状态为 rejected 的 Promise 对象let promise2 = new Promise((resolve, reject) => {setTimeout(() => {reject('第二个 Promise');})})// 状态为 rejected 的 Promise 对象let promise3 = new Promise((resolve, reject) => {reject('第三个 Promise');})// 有任务为 rejected 状态,会执行 Promise.all 返回的新 Promise 对象的 rejected 处理程序// promise2 被 setTimeout 挂起,所以 promise3 先执行,因为 promise3 为 rejected 状态,// 所以 Promise.all 会直接返回,而 promise2的 reject 处理程序的参数不会被记录Promise.all([promise1, promise2, promise3]).then(data => {console.log(data)}).catch(error => {console.log(error)})
</script>

2. Promise.race

以第一个执行的任务结果来决定最终返回的结果,第一个任务的结果状态为 fulfilled 时,Promise.race 会返回一个状态为 fulfilled 的新 Promise 对象,并把第一个任务的 resolve 处理程序的参数,传递给新 Promise 对象的 resolve 处理程序,第一个任务的结果状态为 rejected 时,Promise.race 会返回一个状态为 rejected 的新 Promise 对象,并把第一个任务的 rejected 处理程序的参数,传递给新 Promise 对象的 rejected 处理程序

简单总结就是,race 用来判断第一个被执行的 Promise 的结果

第一个执行的任务的结果状态为 fulfilled:

<script>// 状态为 fulfilled 的 Promise 对象let promise1 = new Promise(resolve => {resolve('成功!');})// 状态为 rejected 的 Promise 对象let promise2 = new Promise((resolve, reject) => {reject('失败!');})// promise1 会先执行,其状态是 fulfilled,所以返回的新 Promise 对象的状态也是 fulfilledPromise.race([promise1, promise2]).then(data => {console.log(data)}).catch(error => {console.log(error)})
</script>

第一个执行的任务的结果状态为 rejected:

<script>// 状态为 fulfilled 的 Promise 对象let promise1 = new Promise(resolve => {setTimeout(()=>{resolve('成功!');})})// 状态为 rejected 的 Promise 对象let promise2 = new Promise((resolve, reject) => {reject('失败!');})// promise1 因为被 setTimeout 挂起了,所以 promise2 会先执行,其状态是 rejected,// 所以返回的新 Promise 对象的状态也是 rejectedPromise.race([promise1, promise2]).then(data => {console.log(data)}).catch(error => {console.log(error)})
</script>

二、模拟 Promise,加深理解


借鉴大牛的代码,地址在下面,其中 then 方法的实现部分,真的是超级超级难理解,只能尽量记录当时的理解思路,希望以后复习时,能马上想回忆起来

注意:代码里没有体现 <微队列>,而是用 setTimeout 的 <宏队列> 来代替

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>MyPromise</title>
</head>
<body>
<script>// Promise 的三种状态const PENDING = 'pending'const FULFILLED = 'fulfilled'const REJECTED = 'rejected'class MyPromise {// 构造器的参数 executor 为回调函数constructor(executor) {this._status = PENDING     // 创建 Promise 对象后的初始状态this._value = undefined    // executor 中传给 resolve/reject 函数的参数,或 then 方法显式 return 的值this._resolveQueue = []    // then 方法收集的 resolve 处理程序队列this._rejectQueue = []     // then 方法收集的 reject 处理程序队列 // 定义函数变量,当作 executor 的第一个参数let _resolve = (val) => {// 函数体封装成闭包,供下面 setTimeout 调用,使其加入 <宏队列> 等主线程空闲执行,// 目的是给 then 方法让步,让其先行收集 resolve 处理程序,// 此处模拟了原版 Promise 调用 resolve 后会加入到 <微队列> 的特性const run = () => {if (this._status !== PENDING) return   // Promise 规定状态只能由 pending 到 fulfilled 或 rejectedthis._status = FULFILLED               // 将状态变更为 fulfilledthis._value = val                      // 存储 executor 中传给 resolve 函数的参数// 这里之所以使用一个数组来存储, 是为了实现,可以给同一个 Promise 对象添加多个 resolve 处理程序的功能while (this._resolveQueue.length) {const callback = this._resolveQueue.shift() // 从 then 方法收集的 resolve 处理程序队列中取出回调函数callback(val) // 执行取出的回调函数,并把之前保存的参数传进去}}// 调用闭包,让其进入 <宏队列> setTimeout(run)}// 定义函数变量,当作 executor 的第二个参数let _reject = (val) => {// 函数体封装成闭包,供下面 setTimeout 调用,使其加入 <宏队列> 等主线程空闲执行,// 目的是给 then 方法让步,让其先行收集 reject 处理程序,// 此处模拟了原版 Promise 调用 reject 后会加入到 <微队列> 的特性const run = () => {if (this._status !== PENDING) return   // 对应规范中的 "状态只能由 pending 到 fulfilled 或 rejected "this._status = REJECTED                // 将状态变更为 rejectedthis._value = val                      // 存储 executor 中传给 reject 函数的参数// 这里之所以使用一个数组来存储, 是为了实现,可以给同一个 Promise 对象添加多个 resolve 处理程序的功能while (this._rejectQueue.length) {const callback = this._rejectQueue.shift()  // 从 then 方法收集的 reject 处理程序队列中取出回调函数callback(val)  // 执行取出的回调函数,并把之前保存的参数传进去}}// 调用闭包,让其进入 <宏队列> setTimeout(run)}// 构造中直接执行 executor 回调函数,并传入前面定义的两个函数变量executor(_resolve, _reject)}// then 方法// 将 resolveFn/rejectFn  函数进一步封装后,收集到 resolve/reject 处理程序队列中then(resolveFn, rejectFn) {// 参数不是 function 类型时,把参数重定义为一个函数,函数会返回上一个 Promise 的 resolve 参数// 或 上一个 then 的返回值typeof resolveFn !== 'function' ? resolveFn = value => value : nulltypeof rejectFn !== 'function' ? rejectFn = error => error : null// 为了满足链式调用, then 方法必须返回 Promise 对象,这样语法上才能继续的写 .then()return new MyPromise((resolve, reject) => {// 定义函数变量,目的就是包装一下 resolveFn 函数,其内部会调用 resolveFn 函数const fulfilledFn = value => {try {// 执行 resolveFn 函数,并获得返回值let x = resolveFn(value)// 1. resolveFn 方法的返回值是 Promise 类型时,按照规定 then 方法需要返回一个和 resolveFn 返回值状态相同的        // Promise 对象, 目的是为了链式调用时,可以正确的调用后续的 resolve/reject 处理程序,因为 resolveFn 返回的 // Promise 的状态可能是 fulfilled,也可能是 rejected,而当前代码被包含在新创建的 MyPromise 内部,其默认状态// 是 pending,不满足返回规定,无法正确调用后续链式的 resolve/reject 处理程序, 所以这个地方需要调用// x.then(resolve, reject), 其目的是,如果后续为链式调用 .then(resolveFn, rejectFn) 的时候,那么会根据// resolveFn 返回的 Promise 对象的状态,决定调用 resolveFn 还是 rejectFn,从而实现链式调用的效果// 2. resolveFn 方法的返回值不是 Promise 类型时, 按照规定需要返回一个状态为 fulfilled 的 Promise// 因为当前代码被包含在新创建的 MyPromise 内部,而新创建的 MyPromise 默认状态是 pending,所以此处要调用// 该 MyPromise 的 resolve 方法,原因有两个,一个是调用 resolve 方法后状态会从 pending 变成 fulfilled,// 另一个是如果后续为链式调用 .then(resolveFn, rejectFn) 的时候,那么调用 resolve 后就会执行后续 then 方// 法的 resolveFn, 从而实现链式调用效果x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)} catch (error) {reject(error)}}// 原理同上const rejectedFn = error => {try {let x = rejectFn(error)x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)} catch (error) {reject(error)}}// 将处理程序加入 resolve/reject 处理程序队列// 此处让我简化了, 只留下了主线逻辑this._resolveQueue.push(fulfilledFn)this._rejectQueue.push(rejectedFn)})}}
</script>
</body>
</html>

模拟 Promise 的代码部分,参考文章链接

相关文章:

JS 异步 ( 二、Promise 的用法、手写模拟 Promise )

文章目录 一、Promise 基础Promise 作用Promise 语法Promise 内部状态值 和 链式调用Promise 是否为异步执行Promise 常用函数或属性 二、模拟 Promise&#xff0c;加深理解 一、Promise 基础 Promise 作用 1. 回调地狱 想知道 Promise 的作用&#xff0c; 需要先了解一个概念叫…...

五分钟学会如何在GitHub上自动化部署个人博客(hugo框架 + stack主题)

上一篇文章&#xff1a; 10分钟学会免费搭建个人博客&#xff08;Hugo框架 stack主题&#xff09; 前言 首先&#xff0c;想要实现这个功能的小伙伴需要完成几个前置条件&#xff1a; 有一个GitHub账号安装了git&#xff0c;并可以通过git推送commit到GitHub上完成第一篇文章…...

【ETCD】【实操篇(十五)】etcd集群成员管理:如何高效地添加、删除与更新节点

etcd 是一个高可用的分布式键值存储&#xff0c;广泛应用于存储服务发现、配置管理等场景。为了确保集群的稳定性和可扩展性&#xff0c;管理成员节点的添加、删除和更新变得尤为重要。本文将指导您如何在etcd集群中处理成员管理&#xff0c;帮助您高效地维护集群节点。 目录 …...

灵当CRM uploadfile.php 文件上传致RCE漏洞复现

0x01 产品简介 灵当CRM是一款专为中小企业打造的智能客户关系管理工具,由上海灵当信息科技有限公司开发并运营。广泛应用于金融、教育、医疗、IT服务、房地产等多个行业领域,帮助企业实现客户个性化管理需求,提升企业竞争力。无论是新客户开拓、老客户维护,还是销售过程管…...

Linux下Java通过JNI调用C++

以下为Demo流程 1.创建Java文件 public class HelloWord {// 声明本地方法public native void sayHello();static {// 加载本地库System.loadLibrary("hello");}public static void main(String[] args) {new HelloWord().sayHello();} } 2.编译生成.h头文件 在H…...

解决:excel鼠标滚动幅度太大如何调节?

在excel里为什么滚动一次跳过很多行呢&#xff1f;很不方便。。。 1. 问题&#xff1a; 一开始单元格从第1行开始&#xff1a; 鼠标轻轻滚动一下后&#xff0c;直接跳到第4行&#xff1a; 鼠标在word和浏览器里都是好好的。在excel里为什么不是滚动一次跳过一行呢&#xff…...

Git和SVN有什么区别?

Git和SVN是两种流行的版本控制系统&#xff0c;它们在实现方式、使用场景和特性上存在一些关键区别&#xff1a; 分布式 vs 集中式&#xff1a; Git&#xff1a;是分布式的版本控制系统。每个开发者都有一个完整的仓库副本&#xff0c;可以离线工作&#xff0c;并在本地进行所有…...

【深度学习基础|pip安装】pip 安装深度学习库常见错误及解决方案,附案例。

【深度学习基础|pip安装】pip 安装深度学习库常见错误及解决方案&#xff0c;附案例。 【深度学习基础|pip安装】pip 安装深度学习库常见错误及解决方案&#xff0c;附案例。 文章目录 【深度学习基础|pip安装】pip 安装深度学习库常见错误及解决方案&#xff0c;附案例。1. 错…...

【ES6复习笔记】解构赋值(2)

介绍 解构赋值是一种非常方便的语法&#xff0c;可以让我们更简洁地从数组和对象中提取值&#xff0c;并且可以应用于很多实际开发场景中。 1. 数组的解构赋值 数组的解构赋值是按照一定模式从数组中提取值&#xff0c;然后对变量进行赋值。下面是一个例子&#xff1a; con…...

Dockerfile的用法

Dockerfile的用法 示例 `Dockerfile`使用 `Dockerfile` 创建 Docker 镜像`Dockerfile` 指令详解其他常用指令总结Dockerfile 是一个文本文件,包含了用于创建 Docker 镜像的一系列指令。这些指令描述了镜像的基础、所安装的软件、文件的复制、环境变量的设置以及其他配置。下面…...

Docker 安装mysql ,redis,nacos

一、Mysql 一、Docker安装Mysql 1、启动Docker 启动&#xff1a;sudo systemctl start dockerservice docker start 停止&#xff1a;systemctl stop docker 重启&#xff1a;systemctl restart docker 2、查询mysql docker search mysql 3、安装mysql 3.1.默认拉取最新版…...

Axure RP 11 详细保姆级安装教程(附安装包)

文章目录 初识&#xff1a;Axure RP 11 安装推荐配置 一、下载安装包 二、安装步骤 1.运行安装程序 2.安装向导&#xff0c;点击【Next】 3.许可协议&#xff0c;勾选【I accept the terms in the License Agreement】&#xff0c;然后点击【Next】 4.确认安装位置&…...

Java处理视频思路

1.首先实现断点续传功能。 断点续传实现思路&#xff1a; 前端对文件分块。前端使用多线程一块一块上传&#xff0c;上传前给服务端发一个消息校验该分块是否上传&#xff0c;如果已上传则不再上传。如果从该断点处断网了&#xff0c;下次上传时&#xff0c;前面的分块已经存在…...

攻防世界 robots

开启场景 根据提示访问/robots.txt&#xff0c;发现了 f1ag_1s_h3re.php 拼接访问 /f1ag_1s_h3re.php 发现了 flag cyberpeace{d8b7025ed93ed79d44f64e94f2527a17}...

DBeaver 咋手动配置sqlite 驱动

目录 1 问题2 下载 1 问题 离线安装了DBeaver 数据库软件&#xff0c;现在需要使用这个数据库打开sqlite 数据库&#xff0c;但是提示没有 驱动&#xff0c;那么我们就需要手动下载驱动&#xff0c;在这个软件里面导入 2 下载 https://repo1.maven.org/maven2/org/xerial/sql…...

RestTemplate关于https的使用详解

RestTemplate关于https的使用详解 一、restTemplate注入到bean里面。 Configuration public class RestTempleConfig {BeanPrimarypublic RestTemplate restTemplate() {return new RestTemplate();}/*** https 请求的 restTemplate* return* throws Exception*/Beanpublic R…...

消息中间件RabbitMQ和kafka

一、RabbitMQ 1、RabbitMQ如何保证消息不丢失 2、RabbitMQ如何解决重复消费的问题 3、RabbitMQ中的死信交换机 4、RabbitMQ消息堆积怎么解决 5、RabbitMQ的高可用机制 二、kafka 1、kafka如何保证消息不丢失和重复消费的问题 2、kafka如何保证消费的顺序性 3、kafka高可用机制…...

学习C++:标识符命名规则

标识符命名规则&#xff1a; 作用&#xff1a;C规定给标识符&#xff08;变量、常量&#xff09;命名时&#xff0c;有一套自己的规则 标识符不能是关键字 标识符只能由字母、数字、下划线组成 第一个字符必须为字母或下划线 标识符中字母区分大小写 &#xff08;给标识符命…...

Bluetooth Spec【0】蓝牙核心架构

蓝牙核心系统由一个主机、一个主控制器和零个或多个辅助控制器组成蓝牙BR/ EDR核心系统的最小实现包括了由蓝牙规范定义的四个最低层和相关协议&#xff0c;以及一个公共服务层协议&#xff1b;服务发现协议&#xff08;SDP&#xff09;和总体配置文件要求在通用访问配置文件&a…...

AppInventor2 ClientSocketAI2Ext 拓展加强版 - 为App提供TCP客户端接入,可发送二进制数据

本文介绍App Inventor 2利用拓展实现TCP/IP协议接入功能&#xff0c;作为网络客户端连接TCP服务器&#xff0c;进行数据通信&#xff08;发送/接收&#xff09;。 // ClientSocketAI2Ext 拓展现状 // 原版拓展名称为&#xff1a;com.gmail.at.moicjarod.aix&#xff0c;是能用…...

栈(C语言)

目录 1. 栈的定义 2. 代码实现 1. 栈的定义 栈属于线性表&#xff0c;具有后进先出的特点&#xff0c;存储结构类似羽毛球桶&#xff0c;可以依次取出最后放入栈中的数据。实现&#xff1a;栈一般采用数组 / 链表实现&#xff0c;但是推荐程度&#xff1a;数组 > 单链表 …...

解决手柄兼容性问题的虚拟手柄驱动方案

解决手柄兼容性问题的虚拟手柄驱动方案 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus 在Windows游戏体验中&#xff0c;手柄兼容性问题常常成为玩家的困扰。…...

远程工作事故树:一次误删库引发的跨国追责

远程协作下的“脆弱”系统深夜&#xff0c;伦敦办公室的数据库工程师在连续工作十二小时后&#xff0c;敲下了一条他以为指向“测试环境”的删除命令。与此同时&#xff0c;上海的测试团队正在为次日的上线进行最后一轮回归验证。六小时后&#xff0c;当阳光照进浦东的办公室&a…...

人工智能创意工作流:Pixel Script Temple 与 AI Agent 协同创作

人工智能创意工作流&#xff1a;Pixel Script Temple 与 AI Agent 协同创作 1. 多智能体协作的艺术革命 当三个专业AI Agent组成创意团队&#xff0c;会产生怎样的化学反应&#xff1f;这套由Pixel Script Temple驱动的协同工作流&#xff0c;正在重新定义数字艺术创作的可能…...

RCS 核心系统架构:AGV 调度“大脑”的底层逻辑

在现代智能物流与制造系统中&#xff0c;RCS&#xff08;Robot Control System&#xff09;作为 AGV 集群的核心调度中枢&#xff0c;扮演着"智慧大脑"的关键角色。不同于单台 AGV 的自主导航&#xff0c;RCS 需要解决多车协同、任务分配、路径规划与冲突避免等复杂问…...

TSMaster安全算法实战:如何用DLL快速实现SeedKey解锁(附常见错误排查)

TSMaster安全算法实战&#xff1a;如何用DLL快速实现Seed&Key解锁&#xff08;附常见错误排查&#xff09; 在汽车电子诊断领域&#xff0c;安全访问机制&#xff08;Seed&Key&#xff09;如同车辆的电子钥匙&#xff0c;是保护ECU数据安全的重要屏障。作为深耕诊断协议…...

Qwen3.5-9B企业应用:法务合同关键条款提取+风险点标注案例

Qwen3.5-9B企业应用&#xff1a;法务合同关键条款提取风险点标注案例 1. 项目背景与价值 在法务工作中&#xff0c;合同审查是一项耗时且容易出错的任务。传统的人工审查方式需要律师逐条阅读合同文本&#xff0c;识别关键条款并标注潜在风险点&#xff0c;这个过程通常需要数…...

基于BANG语言的Sigmoid算子开发与PyTorch集成实战指南

1. BANG语言与Sigmoid算子开发基础 第一次接触寒武纪BANG语言时&#xff0c;我被它类似CUDA但更简洁的语法设计惊艳到了。这种专为MLU硬件设计的异构编程语言&#xff0c;通过在C/C基础上扩展并行计算特性&#xff0c;让开发者能更高效地利用寒武纪芯片的算力资源。 BANG核心语…...

M5Stamp C3 Mate LED驱动库:基于RMT的WS2812B精简控制方案

1. 项目概述M5StampC3LED 是专为 M5Stamp C3 Mate 模块设计的 LED 控制库&#xff0c;其本质是一个轻量级封装层&#xff0c;用于驱动板载的 Adafruit NeoPixel&#xff08;WS2812B 兼容&#xff09;RGB LED。该库不直接实现底层时序协议&#xff0c;而是基于 ESP-IDF 或 Ardui…...

OpenClaw学术研究助手:Qwen3.5-9B-AWQ-4bit解析论文图表数据

OpenClaw学术研究助手&#xff1a;Qwen3.5-9B-AWQ-4bit解析论文图表数据 1. 为什么需要自动化论文图表解析 去年冬天&#xff0c;我在整理一篇关于机器学习模型压缩的综述论文时&#xff0c;遇到了一个典型的研究痛点&#xff1a;需要从32篇相关文献的PDF中提取实验数据表格进…...