摘要:阶段有两个主要功能也会执行时间定时器到达期望时间的回调函数执行事件循环列表里的函数当进入阶段并且没有其余的定时器,那么如果事件循环列表不为空,则迭代同步的执行队列中的函数。如果没有,则等待回调函数进入队列并立即执行。
Event Loop
本文以 Node.js 为例,讲解 Event Loop 在 Node.js 的实现,原文,JavaScript 中的实现大同小异。
什么是 Event Loop ?单线程的 Node.js 能够实现无阻塞IO的原因就是事件循环(Event Loop)。
现在大多数系统内核是多线程的,所以它们可以在后台执行多个操作,当这些操作完成时,内核就会通知 Node.js,而这些操作的回调函数被添加到事件轮询列表(poll queue),并且 Node.js 会在适当的时机执行回调函数。
概览 Event Loop当 Node.js 开始执行时,便初始化 Event Loop,执行过程中会存在许多异步操作,如:REPL、定时器(timers)、调用异步 API(请求,事件监听),在主进程代码执行完后,便开始运行 Event Loop。
下图描述了 Event Loop 中的各个阶段
┌───────────────────────┐ ┌─>│ timers │ 这个阶段执行 `setTimeout()` 和 `setInterval()` 中的回调函数 │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ 这个阶段执行除了 `close` 回调函数以外的几乎所有的 I/0 回调函数 │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ 这个阶段仅仅 Node.js 内部使用 │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ 执行队列中的回调函数、检索新的回调函数 │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ `setImmediate()` 将在这里被调用 │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ `close` 回调函数被调用如:socket.on("close", ...) └───────────────────────┘详解 Event Loop 的各个阶段
setTimeout() 和 setInterval() 都要指定一个运行时间,这个运行时间其实不是确切的运行时间,而是一个期望时间,Event Loop 会在 timers 阶段执行超过期望时间的定时器回调函数,但由于你不确定在其他阶段甚至主进程中的事件执行时间,所以定时器不一定会按时执行。
var asyncApi = function (callback) { setTimeout(callback, 90) } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms setTimeout 被执行`); // 140ms 之后被执行 }, 100); asyncApi(() => { const startCallback = Date.now(); while (Date.now() - startCallback < 50) { // do nothing } })
这个阶段主要执行一些系统操作带来的回调函数,如 TCP 错误,如果 TCP 尝试链接时出现 ECONNREFUSED 错误 ,一些 *nix 会把这个错误报告给 Node.js。而这个错误报告会先进入队列中,然后在 I/O callbacks 阶段执行。
poll 阶段有两个主要功能:
也会执行时间定时器到达期望时间的回调函数
执行事件循环列表(poll queue)里的函数
当 Event Loop 进入 poll 阶段并且没有其余的定时器,那么:
如果事件循环列表不为空,则迭代同步的执行队列中的函数。
如果事件循环列表为空,则判断是否有 setImmediate() 函数待执行。如果有结束 poll 阶段,直接到
check 阶段。如果没有,则等待回调函数进入队列并立即执行。
在 poll 阶段结束之后,执行 setImmediate()。
突然结束的事件的回调函数会在这里触发,如果 socket.destroy(),那么 close 会被触发在这个阶段,也有可能通过 process.nextTick() 来触发。
setImmediate()、setTimeout()、process.nextTick()这里要说明一下 process.nextTick() 是在下次事件循环之前运行,如果把 process.nextTick() 和 setImmediate() 写在一起,那么是 process.nextTick() 先执行。next 比 immediate 快,官方也说这个函数命名有问题,但是因为历史存留没办法解决。
process.nextTick(() => { console.log("nextTick"); }); setImmediate(() => { console.log("setImmediate"); }); setTimeout(() => { console.log("setTimeout"); }, 0) // 执行结果,nextTick, setTimeout, setImmediate // 查看 Node.js 源码,setTimeout(fun, 0) 会转化成 setTimeout(fun, 1),所以在这种简单的情况下,对于不同设备,setImmediate 有可能早于 setTimeout 执行。总结
理解事件循环,会知道 JavaScript 如何无阻塞运行的,以及它简洁的开发思路和事件驱动风格。
作者:肖沐宸,github。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89770.html
摘要:如果当前没有事件也没有定时器事件,则返回。相关资料关于的架构及设计思路的事件讨论了使用线程池异步运行代码。下一篇初窥事件机制的实现二中定时器的实现 在浏览器中,事件作为一个极为重要的机制,给予JavaScript响应用户操作与DOM变化的能力;在Node.js中,事件驱动模型则是其高并发能力的基础。 学习JavaScript也需要了解它的运行平台,为了更好的理解JavaScript的事...
摘要:事件循环了解了在引擎中是如何工作了之后,来看下如何使用异步回调函数来避免代码。从回调函数被放入后秒钟,把移到中。由于事件循环持续地监测调用栈是否已空,此时它一注意到调用栈空了,就调用并创建一个新的调用栈。 听多了JavaScript单线程,异步,V8,便会很想去知道JavaScript是如何利用单线程来实现所谓的异步的。我参考了一些文章,了解到一个很重要的词汇:事件循环(Event L...
摘要:的宿主最开始本身就是浏览器,处理用户的交互事件。既然是单线程的,那就意味着任务需要排队,只有前一个任务执行完毕,下一个任务才能开始,于是就有了任务队列。事件循环有两种用于浏览上下文的事件循环和用于的事件循环。 最近看到Event Loop这个词出现的频率有点高,于是查阅各方资料在此记录一下。 先不说概念,我们来看段代码: console.log(script start); setT...
js异步历史 一个 JavaScript 引擎会常驻于内存中,它等待着我们把JavaScript 代码或者函数传递给它执行 在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,引擎就把代码直接顺次执行了,异步任务都是宿主环境(浏览器)发起的(setTimeout、AJAX等)。 在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排,J...
摘要:曾经的理解首先,是单线程语言,也就意味着同一个时间只能做一件事,那么为什么不是多线程呢这样还能提高效率啊假定同时有两个线程,一个线程在某个节点上编辑了内容,而另一个线程删除了这个节点,这时浏览器就很懵逼了,到底以执行哪个操作呢所以,设计者把 Event Loop曾经的理解 首先,JS是单线程语言,也就意味着同一个时间只能做一件事,那么 为什么JavaScript不是多线程呢?这样还能提...
阅读 1566·2021-09-26 09:46
阅读 2637·2021-09-07 09:59
阅读 2733·2021-09-07 09:59
阅读 1829·2019-08-30 14:20
阅读 906·2019-08-26 13:39
阅读 3120·2019-08-26 12:24
阅读 752·2019-08-26 11:55
阅读 1201·2019-08-23 16:49