JS 异步 ( 一、异步概念、Web worker 基本使用 )

ops/2024/12/27 10:53:21/

文章目录

  • 异步
    • 代码异步执行概念
    • ES6 之前的异步
  • Web worker

异步

代码异步执行概念

通常代码是自上而下同步执行的,既后面的代码必须等待前面的代码执行完才会执行,而异步执行则是将主线程中的某段代码交由子线程去执行,当交给子线程后,主线程就会继续执行后面代码,而不用等待子线程执行完成,异步是程序语言并行执行的一种手段,通常将耗时的任务交由子线程同时处理,从而提升整体任务耗时。

不严谨的对比一下单线程同步和多线程异步的效率提升(不考虑 CPU 核数和时间片切换问题):

请添加图片描述

ES6 之前的异步


在 ES6 的 Web worker 出现之前,Javascript 确实也可以异步开发,但是要知道的是,那时的 Javascript 是单线程的,之所以能够使用多线程实现异步,其实是依靠 <浏览器内核结构> 的多线程,而不是 JS 本身具备多线程特性。

1. 浏览器内核结构

请添加图片描述

浏览器是多个进程共同配合工作的,所谓浏览器内核指的就是其中的渲染进程,进程主要结构如上图,
渲染进程中又有如下几个重要的线程(这些线程并不是JS的,而是浏览器渲染进程的):

线程功能
GUI 渲染线程 (渲染引擎)解析 HTML 和 CSS,从而构建 DOM
JS 执行线程 (JS 引擎)负责执行 Javascript 代码,内含 <任务队列> 和 <事件循环> 两个重要模块
事件触发线程 (DOM 监听)当事件发生后,将事件的回调函数添加到 JS 引擎的 <任务队列>
定时器线程 (Timer 监听)setTimeout、setInterval 等的计时,达到时间后,将计时器的回调函数添加到 JS 引擎的 <任务队列>
XmlHttpRequest 线程 (AJAX)XmlHttpRequest 的监听,当 XmlHttpRequest 对象状态变化时,将 Ajax 回调函数添加到 JS 引擎的 <任务队列>

GUI 线程和 JS 执行线程不能同时执行,遇见 Javascript 代码时,JS 执行引擎运行优先级更高

2. JS 执行线程的运行机制及与其他线程的搭配

请添加图片描述

(1) 主线程按顺序执行代码,当碰见 setTimeoutsetIntervalXmlHttpRequestDOM 事件 等 Api 时,会将其交给
对应的定时器线程、XmlHttpRequest 线程、事件线程处理,然后主线程继续执行后面的代码

(2) 定时器线程、XmlHttpRequest 线程、事件线程的任务触发后,会将回调函数添加至 JS 线程的任务队列中

(3) 主线程内的任务全部执行完成后,会调用事件循环器拉取任务队列中的任务到主线程,至此一个事件循环周期结束

3. 分析 setTimeout 等计时不准的问题

<script>javascript">(function async(){console.log('主线程执行1')setTimeout(function(){console.log('setTimeout 计时结束')}, 3000)console.log('主线程执行2')while(true){}}())// 输出:// 主线程执行1// 主线程执行2
</script>

预想结果是 3 秒后输出 <setTimeout 计时结束>,但在实际结果中,setTimeout 即使达到时间也没有执行,现在明
白了 <JS 执行线程的运行机制及与其他线程的搭配> 的原理,就可以解释这个现象了,因为主线程中有 while(true)
而主线程执行不完,就不会执行事件循环器拉取任务队列中 setTimeout 的回调函数,所以 setTimeout 的回调一直没有被调用


4. 微观队列

在 ES6 之后,<JS 执行线程的运行机制> 的整体流程有一点变化,事件循环器除了调用 <任务队列> 以外, 又多了
一个队列,为了区分,原队列改称为 <宏队列>,新队列称为 <微队列> ,<微队列> 中比较典型的就是状态为 fulfilled
或 rejected 的 Promise 对象的处理函数。在一次事件循环中优先执行 <微队列> 中的任务。

请添加图片描述

举例 - 事件循环器优先执行 <微队列> 中的任务:

<script>javascript">console.log("主线程任务1")// 定时器setTimeout(()=>{console.log("延迟零毫秒的定时任务1")},0)// 状态为 fulfilled 的 PromisePromise.resolve().then(()=>{console.log("状态为 fulfilled 的 Promise")})// 定时器setTimeout(()=>{console.log("延迟零毫秒的定时任务2")},0)console.log("主线程任务2")// 输出:// 主线程任务1// 主线程任务2// 状态为 fulfilled 的 Promise// 延迟零毫秒的定时任务1// 延迟零毫秒的定时任务2
</script>

复杂一点的举例(事件循环每次只获取一个任务):

<script>javascript">console.log("主线程任务1")// 定时器setTimeout(()=>{console.log("延迟零毫秒的定时任务1")},0)// 定时器setTimeout(()=>{// 状态为 fulfilled 的 PromisePromise.resolve().then(()=>{console.log("状态为 fulfilled 的 Promise 1")})},0)// 状态为 fulfilled 的 PromisePromise.resolve().then(()=>{console.log("状态为 fulfilled 的 Promise 2")})// 定时器setTimeout(()=>{console.log("延迟零毫秒的定时任务2")},0)console.log("主线程任务2")// 输出:// 主线程任务1// 主线程任务2// 状态为 fulfilled 的 Promise 2// 延迟零毫秒的定时任务1// 状态为 fulfilled 的 Promise 1// 延迟零毫秒的定时任务2
</script>

Web worker


ES6 以前,JS 是单线程的,所谓异步也都是依赖于浏览器内核的多线程机制,而不是 JS 本身具有多线程特性,这就导致能支持的异步操作很少(定时器线程,事件线程,Ajax线程),ES6 以后新增的 Web worker 功能让 JS 真正的拥有了多线程特性,但是 Web work 创建的子线程有一些使用限制。

限制描述
同源限制子线程的 JS 脚本,必须和主线程的脚本文件同源
DOM限制不能对页面元素操作,包括不能使用弹出框等,可以理解为 JS 子线程无法使用渲染进程的渲染引擎

1. 基本语法

主线程脚本

<script>javascript">// 用来创建并启动一个 JS 子线程,参数为 JS 脚本文件 URLconst work = new Worker("./worker.js")// 给子线程发送数据,参数任何类型都可以work.postMessage('发送给子线程的消息')// 监听子线程是否有消息返回,event 为事件对象,event.data 可以获取子线程返回的数据work.onmessage = (event)=>{console.log('接收到的子线程处理结果:' + event.data)// 关闭子线程work.terminate();}
</script>

子线程脚本文件 worker.js

javascript">console.log('子线程启动')
// 监听主线程是否有消息发送过来,event 为事件对象,event.data 可以获取主线程发送的数据
addEventListener('message', (event) => {console.log('子线程接到消息:' + event.data)// 向主线程发送数据postMessage('处理完成!')// 关闭子线程自身(和 terminate 功能一样,防止主线程调用后忘记关闭,所以此处也写一份 )close()
})

2. 同一文件内使用 Web worker

先说思路,主线程和子线程要分别写在不同的 script 标签对儿中,然后主线程读取子线程标签对儿中的内容,并将其创建成 Blob 类型(BlobFile 类型的父类,所以 Blob 也可以简单理解为文件类型),然后对该文件对象(Blob)生成 url,最后 worker 访问该 url

再说需要注意的东西:
(1) 子线程的 script 脚本要写在主线程 script 脚本之前,防止主线程中读取不到子线程的 script 标签
(2) 子线程的 script 标签的 type 属性,要给一个 type 规定的合法值以外的值(本人喜欢给 web-worker),如果是
合法值,就会被 JS 线程 ( JS 引擎 ) 识别,然后会直接运行其内容,而我们预想的执行时机是主线程调用后执行

<!-- 子线程脚本 -->
<script id="worker" type="web-worker">javascript">console.log('子线程启动')// 监听主线程是否有消息发送过来,event 为事件对象,event.data 可以获取主线程发送的数据addEventListener('message', (event) => {console.log('子线程接到消息:' + event.data)// 向主线程发送数据postMessage('处理完成!')// 关闭子线程自身(和 terminate 功能一样,防止主线程调用后忘记关闭,所以此处也写一份 )close()})
</script>
<!-- 主线程脚本 -->
<script>javascript">// 读取子线程脚本内容, 将其转换成二进制类型(Blob 是 File 类型的父类,所以 Blob 也可以理解为类文件类型)var blob = new Blob([document.querySelector("#worker").textContent]);// 针对 blob 文件生成 URL var url = window.URL.createObjectURL(blob);// 用来创建并启动一个 JS 子线程,参数为生成的 URLconst work = new Worker(url)// 给子线程发送数据,参数任何类型都可以work.postMessage('发送给子线程的消息')// 监听子线程是否有消息返回,event 为事件对象,event.data 可以获取子线程返回的数据work.onmessage = (event)=>{console.log('接收到的子线程处理结果:' + event.data)// 关闭子线程work.terminate();}
</script>

http://www.ppmy.cn/ops/145355.html

相关文章

通过GRE协议组建VPN网络

GRE&#xff08;Generic Routing Encapsulation&#xff0c;通用路由封装协议&#xff09;协议是一种简单而有效的封装协议&#xff0c;它在网络中的广泛应用&#xff0c;比如在构建VPN网络。   GRE是一种封装协议&#xff0c;它允许网络层协议&#xff08;如IP&#xff09;的…

【gulp】gulp 的基本使用

gulp 是一个基于node的自动化打包构建工具&#xff0c;前端开发者可以使用它来处理常见任务&#xff1a; 创建项目 进入项目 npm init -ynpm i gulp -g &#xff08;使用命令 gulp&#xff09;npm i gulp -D # 开发依赖&#xff08;前端工具都是开发依赖 本地安装 代…

面试问题-华勤技术(软件开发岗)

博主base无锡&#xff0c;刚考完研&#xff0c;顺手投了简历到华勤的校招邮箱&#xff0c;然后第二天上午发信息笔试&#xff0c;考的是c语言/c测试卷&#xff0c;只有选择题和判断题&#xff0c;然后是文段阅读理解卷&#xff0c;数据图表计算卷以及性格测试卷&#xff1b;难度…

centos单机部署seata

文章目录 场景分析下载seata包启动 场景 centos7.9 jdk17 安装部署seata 分析 jdk和seata的版本对应关系如图 JDK版本 推荐 Seata 版本 理由 JDK 8 任何 Seata 版本 JDK 8 是 Seata 长期支持的版本&#xff0c;兼容性最好。 JDK 11 Seata 1.2.0 适合需要长期支持且性能较高的应…

Unity 踩坑记录 将Image 的 Image Type 设置成 sliced 不显示图片

将Image 的 Image Type 设置成 sliced 不显示图片 检查 image 自身的 pixels per Unity multplier 的值 和canvas reference pixels per 的值&#xff08;默认100&#xff09;

每日一练 | DHCP 客户端续约过程

01 真题题目 在 DHCP 运行过程中&#xff0c;如果客户端 IP 地址在租约过去 87.5%还没有完成续约的话&#xff0c;客户端将发送什么报文进行再次续约&#xff1f; A. DHCPdiscover 广播报文 B. DHCP release 单播报文 C. DHCPrequest 广播报文 D. DHCPrequest 单播报文 02 真题…

springboot 上传图片 转存成webp

第一步先引入包 <!-- webp-imageio 依赖 --><dependency><groupId>org.sejda.imageio</groupId><artifactId>webp-imageio</artifactId><version>0.1.6</version></dependency>下面就是上传的时候处理的了 /*** 通用上传…

【每日学点鸿蒙知识】私仓搭建、resources创建文件夹、hvigor如何动态设置版本、SM3摘要算法、SP存储报错等

【每日学点鸿蒙知识】私仓搭建、resources创建文件夹、hvigor如何动态设置版本、SM3摘要算法、SP存储报错等 1、OH私仓如何创建&#xff1f; 可以参照以下文档来搭建OH私仓&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-ohpm-repo-quic…