目录
事件
事件循环
事件循环的阻塞
事件循环举例
栈和消息队列
作业队列和promise
process.nextTick()
setImmediate()
setInterval()
首页 web前端 js教程 了解nodejs中的事件和事件循环

了解nodejs中的事件和事件循环

Dec 08, 2020 pm 05:40 PM
node.js 事件 事件监听 事件驱动 异步编程

了解nodejs中的事件和事件循环

相关推荐:《nodejs 教程

熟悉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)
登录后复制

更多编程相关知识,请访问:编程视频!!

以上是了解nodejs中的事件和事件循环的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何用 C++ 函数实现异步编程? 如何用 C++ 函数实现异步编程? Apr 27, 2024 pm 09:09 PM

摘要:C++中的异步编程允许多任务处理,无需等待耗时操作。使用函数指针创建指向函数的指针。回调函数在异步操作完成时被调用。boost::asio等库提供异步编程支持。实战案例演示了如何使用函数指针和boost::asio实现异步网络请求。

C++ 函数在并发编程中的事件驱动机制? C++ 函数在并发编程中的事件驱动机制? Apr 26, 2024 pm 02:15 PM

并发编程中的事件驱动机制通过在事件发生时执行回调函数来响应外部事件。在C++中,事件驱动机制可用函数指针实现:函数指针可以注册回调函数,在事件发生时执行。lambda表达式也可以实现事件回调,允许创建匿名函数对象。实战案例使用函数指针实现GUI按钮点击事件,在事件发生时调用回调函数并打印消息。

Java框架异步编程中的常见问题与解决方案 Java框架异步编程中的常见问题与解决方案 Jun 04, 2024 pm 05:09 PM

Java框架异步编程中常见的3个问题和解决方案:回调地狱:使用Promise或CompletableFuture以更直观的风格管理回调。资源竞争:使用同步原语(如锁)保护共享资源,并考虑使用线程安全集合(如ConcurrentHashMap)。未处理异常:明确处理任务中的异常,并使用异常处理框架(如CompletableFuture.exceptionally())处理异常。

golang框架如何处理并发和异步编程? golang框架如何处理并发和异步编程? Jun 02, 2024 pm 07:49 PM

Go框架利用Go的并发和异步特性提供高效处理并发和异步任务的机制:1.通过Goroutine实现并发,允许同时执行多个任务;2.通过通道实现异步编程,在不阻塞主线程的情况下执行任务;3.适用于实战场景,如并发处理HTTP请求、异步获取数据库数据等。

jQuery中如何实现select元素的改变事件绑定 jQuery中如何实现select元素的改变事件绑定 Feb 23, 2024 pm 01:12 PM

jQuery是一个流行的JavaScript库,可以用来简化DOM操作、事件处理、动画效果等。在web开发中,经常会遇到需要对select元素进行改变事件绑定的情况。本文将介绍如何使用jQuery实现对select元素改变事件的绑定,并提供具体的代码示例。首先,我们需要使用标签来创建一个包含选项的下拉菜单:

Python异步编程: 实现高效并发的异步代码之道 Python异步编程: 实现高效并发的异步代码之道 Feb 26, 2024 am 10:00 AM

1.为什么要使用异步编程?传统编程使用阻塞式I/O,这意味着程序会等待某个操作完成,然后才能继续执行。这对于处理单个任务来说可能很有效,但对于处理大量任务时,可能会导致程序变慢。异步编程则打破了传统阻塞式I/O的限制,它使用非阻塞式I/O,这意味着程序可以将任务分发到不同的线程或事件循环中执行,而无需等待任务完成。这允许程序同时处理多个任务,提高程序的性能和效率。2.python异步编程的基础Python异步编程的基础是协程和事件循环。协程是允许函数在暂停和恢复之间切换的函数。事件循环则负责调度

PHP 异步编程的优势与劣势? PHP 异步编程的优势与劣势? May 06, 2024 pm 10:00 PM

异步编程在PHP中的优势包括更高的吞吐量、更低的延迟、更好的资源利用和可扩展性。其劣势包括复杂性、调试难度和有限的库支持。实战案例中,ReactPHP用于处理WebSocket连接,展示了异步编程的实际应用。

Python异步编程: 揭秘异步编程的本质, 优化代码性能 Python异步编程: 揭秘异步编程的本质, 优化代码性能 Feb 26, 2024 am 11:20 AM

异步编程,英文AsynchronousProgramming,是指程序中的某些任务可以并发地执行,而无需等待其他任务完成,从而提高程序的整体运行效率。在python中,asyncio模块是实现异步编程的主要工具,它提供了协程、事件循环和其他异步编程所需的组件。协程:协程(Coroutine)是一种特殊的函数,它可以被暂停然后恢复执行,就像线程一样,但协程比线程更轻量级,内存消耗更低。协程由async关键字声明,并在await关键字处暂停执行。事件循环:事件循环(EventLoop)是异步编程中的

See all articles