nodejs事件和事件循环简介 nodejs中的事件循环的执行顺序
bigegpt 2024-10-08 00:54 23 浏览
简介
熟悉javascript的朋友应该都使用过事件,比如鼠标的移动,鼠标的点击,键盘的输入等等。我们在javascript中监听这些事件,从而触发相应的处理。
同样的nodejs中也有事件,并且还有一个专门的events模块来进行专门的处理。
同时事件和事件循环也是nodejs构建异步IO的非常重要的概念。
今天我们来详细了解一下。
事件
nodejs为事件提供了一个专门的模块:lib/events.js。
还记得我们在讲使用nodejs构建web服务器吗?
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('welcome to www.flydean.com\n')
})
这里,每个请求都会触发request事件。
nodejs的核心API是基于异步事件驱动来进行架构的,所以nodejs中有非常多的事件。
比如:net.Server 会在每次有新连接时触发事件,fs.ReadStream 会在打开文件时触发事件,stream会在数据可读时触发事件。
我们看一下怎么来构建一个nodejs的事件:
const EventEmitter = require('events')
const eventEmitter = new EventEmitter()
events常用的方法有两个,分别是on和emit。
on用来监听事件,emit用来触发事件。
eventEmitter.on('fire', () => {
console.log('开火')
})
eventEmitter.emit('fire')
emit还可以带参数,我们看下一个参数的情况:
eventEmitter.on('fire', who => {
console.log(`开火 ${who}`)
})
eventEmitter.emit('fire', '美帝')
再看看两个参数的情况:
eventEmitter.on('fire', (who, when) => {
console.log(`开火 ${who} ${when}`)
})
eventEmitter.emit('fire', '川建国','now')
默认情况下,EventEmitter以注册的顺序同步地调用所有监听器。这样可以确保事件的正确排序,并有助于避免竞态条件和逻辑错误。
如果需要异步执行,则可以使用setImmediate() 或者 process.nextTick()来切换到异步执行模式。
eventEmitter.on('fire', (who, when) => {
setImmediate(() => {
console.log(`开火 ${who} ${when}`);
});
})
eventEmitter.emit('fire', '川建国','now')
除此之外,events还支持其他几个方法:
once(): 添加单次监听器
removeListener() / off(): 从事件中移除事件监听器
removeAllListeners(): 移除事件的所有监听器
事件循环
我们知道nodejs的代码是运行在单线程环境中的,每次只会去处理一件事情。
这一种处理方式,避免了多线程环境的数据同步的问题,大大的提升了处理效率。
所谓事件循环,就是指处理器在一个程序周期中,处理完这个周期的事件之后,会进入下一个事件周期,处理下一个事件周期的事情,这样一个周期一个周期的循环。
事件循环的阻塞
如果我们在事件处理过程中,某个事件的处理发生了阻塞,则会影响其他的事件的执行,所以我们可以看到在JS中,几乎所有的IO都是非阻塞的。这也是为什么javascript中有这么多回调的原因。
事件循环举例
我们看一个简单的事件循环的例子:
const action2 = () => console.log('action2')
const action3 = () => console.log('action3')
const action1 = () => {
console.log('action1')
action2()
action3()
}
action1()
上面的代码输出:
action1
action2
action3
栈和消息队列
我们知道函数间的调用是通过栈来实现的,上面的例子中,我们的调用顺序也是通过栈来实现的。
但并不是函数中所有的方法都会入栈,还有一些方法会被放入消息队列。
我们再举一个例子:
const action2 = () => console.log('action2')
const action3 = () => console.log('action3')
const action1 = () => {
console.log('action1')
setTimeout(action2, 0)
action3()
}
action1()
上面的代码运行结果:
action1
action3
action2
结果不一样了。这是因为settimeout触发了定时器,当定时器到期的时候,回调函数会被放入消息队列中等待被处理,而不是放入栈中。
事件循环会优先处理栈中的事件,只有栈中没有任何数据的时候,才会去转而消费消息队列中的事件。
虽然上面例子中setTimeout的timeout时间是0,但是还是要等到action3执行完毕才能执行。
注意,setTimeout中的timeout并不是在当前线程进行等待的,它是由浏览器或者其他JS执行环境来调用的。
作业队列和promise
ES6中的Promise引入了作业队列的概念,使用作业队列将会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。
举个例子:
const action2 = () => console.log('action2')
const action3 = () => console.log('action3')
const action1 = () => {
console.log('action1')
setTimeout(action2, 0)
new Promise((resolve, reject) =>
resolve('应该在action3之后、action2之前')
).then(resolve => console.log(resolve))
action3()
}
action1()
输出结果:
action1
action3
应该在action3之后、action2之前
action2
这是因为,在当前函数结束之前 resolve 的 Promise 会在当前函数之后被立即执行。
也就是说先执行栈,再执行作业队列,最后执行消息队列。
process.nextTick()
先给大家一个定义叫做tick,一个tick就是指一个事件周期。而process.nextTick()就是指在下一个事件循环tick开始之前,调用这个函数:
process.nextTick(() => {
console.log('i am the next tick');
})
所以nextTick一定要比消息队列的setTimeout要快。
setImmediate()
nodejs提供了一个setImmediate方法,来尽快的执行代码。
setImmediate(() => {
console.log('I am immediate!');
})
setImmediate中的函数会在事件循环的下一个迭代中执行。
setImmediate() 和 setTimeout(() => {}, 0)的功能基本上是类似的。它们都会在事件循环的下一个迭代中运行。
setInterval()
如果想要定时执行某些回调函数,则需要用到setInterval。
setInterval(() => {
console.log('每隔2秒执行一次');
}, 2000)
要清除上面的定时任务,可以使用clearInterval:
const id = setInterval(() => {
console.log('每隔2秒执行一次');
}, 2000)
clearInterval(id)
注意,setInterval是每隔n毫秒启动一个函数,不管该函数是否执行完毕。
如果一个函数执行时间太长,就会导致下一个函数同时执行的情况,怎么解决这个问题呢?
我们可以考虑在回调函数内部再次调用setTimeout,这样形成递归的setTimeout调用:
const myFunction = () => {
console.log('做完后,隔2s再次执行!');
setTimeout(myFunction, 2000)
}
setTimeout(myFunction, 2000)
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/nodejs-event/
本文来源:flydean的博客
欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
相关推荐
- pyproject.toml到底是什么东西?(py trim)
-
最近,在Twitter上有一个Python项目的维护者,他的项目因为构建失败而出现了一些bug(这个特别的项目不提供wheel,只提供sdist)。最终,发现这个bug是由于这个项目使用了一个pypr...
- BDP服务平台SDK for Python3发布(bdp数据平台)
-
下载地址https://github.com/imysm/opends-sdk-python3.git说明最近在开发和bdp平台有关的项目,用到了bdp的python的sdk,但是官方是基于p...
- Python-for-Android (p4a):(python-for-android p4a windows)
-
一、Python-for-Android(p4a)简介Python-for-Android(p4a),一个强大的开发工具,能够将你的Python应用程序打包成可在Android设备上运行...
- Qt for Python—Qt Designer 概览
-
前言本系列第三篇文章(QtforPython学习笔记—应用程序初探)、第四篇文章(QtforPython学习笔记—应用程序再探)中均是使用纯代码方式来开发PySide6GUI应用程序...
- Python:判断质数(jmu-python-判断质数)
-
#Python:判断质数defisPrime(n):foriinrange(2,n):ifn%i==0:return0re...
- 为什么那么多人讨厌Python(为什么python这么难)
-
Python那么棒,为什么那么多人讨厌它呢?我整理了一下,主要有这些原因:用缩进替代大括号许多人抱怨Python完全依赖于缩进来创建代码块,代码多一点就很难看到函数在哪里结束,那么你就需要把一个函数拆...
- 一文了解 Python 中带有 else 的循环语句 for-else/while-else
-
在本文中,我们将向您介绍如何在python中使用带有else的for/while循环语句。可能许多人对循环和else一起使用感到困惑,因为在if-else选择结构中else正常...
- python的numpy向量化语句为什么会比for快?
-
我们先来看看,python之类语言的for循环,和其它语言相比,额外付出了什么。我们知道,python是解释执行的。举例来说,执行x=1234+5678,对编译型语言,是从内存读入两个shor...
- 开眼界!Python遍历文件可以这样做
-
来源:【公众号】Python技术Python对于文件夹或者文件的遍历一般有两种操作方法,一种是至二级利用其封装好的walk方法操作:import osfor root,d...
- 告别简单format()!Python Formatter类让你的代码更专业
-
Python中Formatter类是string模块中的一个重要类,它实现了Python字符串格式化的底层机制,允许开发者创建自定义的格式化行为。通过深入理解Formatter类的工作原理和使用方法,...
- python学习——038如何将for循环改写成列表推导式
-
在Python里,列表推导式是一种能够简洁生成列表的表达式,可用于替换普通的for循环。下面是列表推导式的基本语法和常见应用场景。基本语法result=[]foriteminite...
- 详谈for循环和while循环的区别(for循环语句与while循环语句有什么区别)
-
初九,潜龙勿用在刚开始使用python循环语句时,经常会遇到for循环和while循环的混用,不清楚该如何选择;今天就对这2个循环语句做深入的分析,让大家更好地了解这2个循环语句以方便后续学习的加深。...
- Python编程基础:循环结构for和while
-
Python中的循环结构包括两个,一是遍历循环(for循环),一是条件循环(while循环)。遍历循环遍历循环(for循环)会挨个访问序列或可迭代对象的元素,并执行里面的代码块。foriinra...
- 学习编程第154天 python编程 for循环输出菱形图
-
今天学习的是刘金玉老师零基础Python教程第38期,主要内容是python编程for循环输出菱形※。(一)利用for循环输出菱形形状的*号图形1.思路:将菱形分解为上下两个部分三角形图案,分别利用...
- python 10个堪称完美的for循环实践
-
在Python中,for循环的高效使用能显著提升代码性能和可读性。以下是10个堪称完美的for循环实践,涵盖数据处理、算法优化和Pythonic编程风格:1.遍历列表同时获取索引(enumerate...
- 一周热门
- 最近发表
-
- pyproject.toml到底是什么东西?(py trim)
- BDP服务平台SDK for Python3发布(bdp数据平台)
- Python-for-Android (p4a):(python-for-android p4a windows)
- Qt for Python—Qt Designer 概览
- Python:判断质数(jmu-python-判断质数)
- 为什么那么多人讨厌Python(为什么python这么难)
- 一文了解 Python 中带有 else 的循环语句 for-else/while-else
- python的numpy向量化语句为什么会比for快?
- 开眼界!Python遍历文件可以这样做
- 告别简单format()!Python Formatter类让你的代码更专业
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- libcrypto.so (74)
- linux安装minio (74)
- ubuntuunzip (67)
- vscode使用技巧 (83)
- secure-file-priv (67)
- vue阻止冒泡 (67)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)