JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop?-阿里云开发者社区

开发者社区> jerrywangsap> 正文

JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop?

简介: JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop?
+关注继续查看

原文


使用像 JavaScript 这样的语言进行编程时,最重要但也经常被误解的部分之一是如何表达和操作一段需要某段时间才能完成执行的程序行为。


这不仅仅是从 for 循环开始到 for 循环结束发生的事情,这当然需要一些时间(微秒到毫秒)才能完成。它是关于当你的程序的一部分现在运行而你的程序的另一部分稍后运行时会发生什么。在程序的两部分分别得到执行的时间间隙,存在着一个 gap.


实际上,所有编写过的重要程序(尤其是用 JS 编写的)都必须以某种方式管理这个 gap,无论是等待用户输入、从数据库或文件系统请求数据、通过网络发送数据以及等待响应,或以固定的时间间隔执行重复的任务(如动画)。通过所有这些不同的方式,您的程序必须及时管理状态。


异步编程从 JS 开始就已经存在了,这是肯定的。但是大多数 JS 开发人员从来没有真正仔细考虑过它是如何以及为什么会出现在他们的程序中,或者探索各种其他方法来处理它。足够好的方法一直是不起眼的回调函数。直到今天,许多人仍坚持认为回调已绰绰有余。


但是随着 JS 的范围和复杂性不断增长,为了满足在浏览器和服务器以及介于两者之间的所有可能的设备中运行的一流编程语言不断扩大的需求,我们管理异步的痛苦正变得越来越严重,他们迫切需要更有能力和更合理的方法。


我们必须更深入地了解异步是什么以及它如何在 JS 中运行。


a program in chunks

你可以在一个 .js 文件中编写你的 JS 程序,但你的程序几乎肯定由几个块组成,其中只有一个现在要执行,其余的将稍后执行。 每个块最常见的单位是函数。


大多数刚接触 JS 的开发人员似乎都有的问题是,“later”不会严格地发生在“now”之后。 换句话说,根据定义,当前无法完成的任务将异步完成,因此我们不会像您直观地期望或想要的那样有阻塞行为。


考虑:

image.png

您可能知道标准 Ajax 请求不是同步完成的,这意味着 ajax(…) 函数还没有任何返回值以分配给 data 变量。 如果 ajax(…) 可以阻塞直到响应回来,那么 data = … 赋值会正常工作。


但这不是我们使用 Ajax 的方式。 我们现在发出一个异步的 Ajax 请求,直到稍后我们才会得到结果。


从现在到以后“等待”的最简单(但绝对不仅仅是,甚至最好!)方法是使用一个函数,通常称为回调函数:

image.png

看这段代码:

image.png

这个程序有两个部分:现在将运行的内容和稍后运行的内容。 这两个块是什么应该很明显。

现在立即执行的代码块:

image.png

稍后异步执行的代码块:

image.png

一旦您执行程序, now 块就会立即运行。 但是 setTimeout(…) 也会设置一个事件(超时)稍后发生,因此 later() 函数的内容将在稍后的时间(从现在起 1,000 毫秒)执行。


任何时候您将一部分代码包装到一个函数中并指定它应该响应某些事件(计时器、鼠标单击、Ajax 响应等)而执行时,您正在创建代码的“later”部分,从而引入异步到你的程序。


Async Console

console.log 到底是同步输出还是异步输出?


没有关于 console.* 方法如何工作的规范或一组要求——它们不是 JavaScript 的正式组成部分,而是由托管环境添加到 JS 中。


因此,不同的浏览器和 JS 环境有着各自的实现,这有时会导致混乱的行为。


特别是,有一些浏览器和一些条件,console.log(…) 实际上并没有立即输出它给出的内容。 这可能发生的主要原因是因为 I/O 是许多程序(不仅仅是 JS)的一个非常缓慢和阻塞的部分。 因此,浏览器在后台异步处理控制台 I/O 可能会表现得更好(从页面/UI 角度来看),而您甚至可能不知道发生了这种情况。


一个不太常见但可能的场景,可以观察到这种情况(不是从代码本身而是从外部):

image.png

我们通常希望在 console.log(…) 语句的确切时刻看到 a 对象被快照,打印类似 { index: 1 } 的内容,这样在 a.index++ 发生时的下一个语句中,它正在修改与 a. 的输出不同的东西,或者完全不同的东西。


大多数情况下,前面的代码可能会在您的开发人员工具的控制台中生成您所期望的对象表示。但同样的代码可能会在浏览器认为需要将控制台 I/O 推迟到后台的情况下运行,在这种情况下,当对象在浏览器控制台中表示时,a.index++已经发生了,它显示 { index: 2 }。


在什么条件下控制台 I/O 将被推迟,甚至是否可以观察到,这是一个不断变化的目标。请注意 I/O 中这种可能的异步性,以防您在调试中遇到问题,其中在 console.log(…) 语句之后修改了对象,但您看到意外的修改出现。


Event Loop

让我们做出一个(也许令人震惊的)声明:尽管您显然能够编写异步 JS 代码(例如我们刚刚看到的超时),但直到最近(ES6),JavaScript 本身实际上从未有任何内置的异步的直接概念.


什么!?这似乎是一个疯狂的主张,对吧?事实上,这是非常正确的。 JS 引擎本身从来没有做过任何事情,只是在任何给定的时刻,在被要求时执行你的程序的单个块。


被谁要求执行呢?这个问题很关键。


JS 引擎不是孤立运行的。它在托管环境中运行,对于大多数开发人员来说,这是典型的 Web 浏览器。在过去的几年里(但绝不是唯一的),JS 通过 Node.js 之类的东西从浏览器扩展到其他环境,例如服务器。事实上,如今 JavaScript 被嵌入到各种设备中,从机器人到灯泡。


但是所有这些环境的一个共同“线程”是它们中有一种机制来处理随着时间的推移来执行多个程序块,在每个时间点调用JS 引擎。这个线程称为事件循环。


换句话说,JS 引擎并没有与生俱来的时间感,而是一个任意 JS 片段的按需执行环境。总是安排“事件”(即 JS 代码执行)的是执行 JavaScript 代码的托管环境。


因此,例如,当您的 JS 程序发出 Ajax 请求以从服务器获取一些数据时,您在函数中设置响应代码(通常称为回调),JS 引擎告诉托管环境,“嘿,我现在将暂停执行,但是每当您完成该网络请求并且您有一些数据时,请回调此函数。”


然后浏览器被设置为监听来自网络的响应,当它有东西给你时,浏览器将回调函数插入到事件循环中,以此来调度回调函数的执行。


那么什么是事件循环呢?


让我们首先通过一些假代码来概念化它。


事件循环(event loop)的逻辑可以用下面的伪代码来表示:

image.png

当然,这是为了说明概念而大大简化的伪代码。但这应该足以帮助获得更好的理解。


如您所见,while 循环代表了一个持续运行的循环,该循环的每次迭代称为一个滴答。对于每个滴答声,如果一个事件在队列中等待,它就会被从队列里摘下并执行。这些事件是您的函数回调。


重要的是要注意 setTimeout(…) 不会将您的回调放在事件循环队列中。它的作用是设置一个计时器;当计时器到期时,环境会将您的回调放入事件循环中,以便将来某个滴答声将其拾取并执行。


如果此时事件循环中已经有 20 个项目怎么办?您的回调等待。它排在其他人后面——通常没有用于抢占队列和跳过队列的路径。这解释了为什么 setTimeout(…) 计时器可能无法以完美的时间精度触发。您可以保证(粗略地说)您的回调不会在您指定的时间间隔之前触发,但它可以在该时间或之后发生,具体取决于事件队列的状态。


因此,换句话说,您的程序通常被分解成许多小块,这些小块在事件循环队列中一个接一个地发生。从技术上讲,与您的程序不直接相关的其他事件也可以在队列中交错。


Parallel Threading

将术语“异步 async”和“并行 parallel”混为一谈是很常见的,但它们实际上是完全不同的。 请记住,异步是关于现在和以后之间的gap. 但并行是指事物能够同时(simultaneously)发生。


最常见的并行计算工具是进程和线程。 进程和线程独立执行,也可能同时执行:在不同的处理器上,甚至在不同的计算机上,但多个线程可以共享单个进程的内存。


相比之下,事件循环将其工作分解为任务并串行执行,不允许并行访问和更改共享内存。 并行和串行可以在不同线程中以协作事件循环的形式共存。


并行执行线程的交织和异步事件的交织发生在非常不同的粒度级别。


例如:

image.png

虽然 later() 的全部内容将被视为单个事件循环队列条目,但在考虑运行此代码的线程时,实际上可能有十几种不同的低级操作。 例如,answer = answer * 2 需要首先加载 answer 的当前值,然后将 2 放在某处,然后执行乘法,然后取结果并将其存储回 answer。


在单线程环境中,线程队列中的项是低级操作真的没有关系,因为没有什么可以中断线程。 但是如果你有一个并行系统,其中两个不同的线程在同一个程序中运行,你很可能会出现不可预测的行为。


考虑下面这段代码:

image.png


image.png


image.png


image.pngimage.pngimage.pngimage.png

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较
原文:C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较 使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新   使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和不夸跨线程执行UI更新报错的最佳实践,附加几种其他方式比较 由于是Winform代码和其他原因,本文章只做代码截图演示,不做界面UI展示,当然所有代码都会在截图展示。
3313 0
DevExpress学习01——下载与安装
记得刚接触编程时,虽然实现了功能,但用户界面十分丑陋,老师叫我们美化一下界面,不要千篇一律,当时觉得能够写出来功能就洋洋得意了,不觉得界面丑陋。后来,在程序比赛中,我接触了一种第三方控件,它可以快速实现控件、皮肤的美化,它就是以前常用的DotNetBar,其入门使用方法见:http://www.cnblogs.com/liweis/p/4195070.html DotNetBar下载地址: 早就听说了DevExpress比较强大了,今天终于来尝试一把。
1414 0
DevExpress学习03——label控件的背景色问题
今天使用了DevExpress的labelControl,发现拖放上去,其背景色和主窗体的背景一样,非常不谐调,把BackColor设置为透明也不行(Windows中的Label可以)。 没有办法,我用颜色拾取器,拾取到上方面板颜色是RGB(80,80,80),于是将labelControl背景色改为RGB(80,80,80)颜色就统一了。
974 0
DevExpress学习02——DevExpress 14.1的汉化
汉化资源: 汉化补丁:dxKB_A421_DXperience_v14.1_(2014-06-09):http://www.t00y.com/file/86576990 汉化工具:DXperienceUniversal-14.
1208 0
线程的强制执行|学习笔记
快速学习线程的强制执行
26 0
SQL Serever学习13——数据库编程语言
编程基础 注释 注释命名来对一些语句进行说明,便于日后维护或者其他用户理解,注释不会执行。 单行注释 SELECT GETDATE() --查询当前日期 多行注释 /* 注释有助于 理解操作的内容 查询当前日期 */ SELECT GETDATE() 变量 在T-SQL执行命令时,可...
979 0
SQL Serever学习14——存储过程和触发器
存储过程 在数据库中很多查询都是大同小异,编写他们费时费力,将他们保存起来,以后执行就很方便了,把SQL语句“封装”起来。 存储过程的概念 存储过程是一组SQL语句集,经过编译存储,可以”一次编译,多次执行“。
1124 0
Node.js学习笔记(六、事件)
Node.js学习笔记(六、事件)
11 0
+关注
2628
文章
0
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载