What’s this post about?
With JavaScript approaching near-ubiquity as the scripting language of the web browser, it benefits you to have a basic understanding of its event-driven interaction model and how it differs from the request-response model typically found in languages like Ruby, Python, and Java. In this post, I’ll explain some core concepts of the JavaScript concurrency model, including its event loop and message queue in hopes of improving your understanding of a language you’re probably already writing but perhaps don’t fully understand.
随着网页浏览器脚本语言的javascript的日益普遍(几乎无所不在),了解它的时间驱动模式(event-driven interaction model )和请求响应模式(request-response model)的区别也变得更加有益.这篇文章中,我将解释一下javascript的核心观念(core concepts),包括,事件轮询( event loop),消息队列(message queue)希望能使你更加明白这门语言,可能你在用它,但你不太明白.
Who is this post for? (这篇文章为谁准备)
This post is aimed at web developers who are working with (or planning to work with) JavaScript in either the client or the server. If you’re already well-versed in event loops then much of this article will be familiar to you. For those of you who aren’t, I hope to provide you with a basic understanding such that you can better reason about the code you’re reading and writing day-to-day.
这篇文章的服务人群是js开发者,或是想做js开发的,不管前端还是后端.如果你非常精通js,你可能看到这篇文章的内容比较熟悉,对于不太明白的同学们,我希望这篇文章能让你在读代码和写代码上更上一层楼.
Non-blocking I/O(非阻塞I/O)
In JavaScript, almost all I/O is non-blocking. This includes HTTP requests, database operations and disk reads and writes; the single thread of execution asks the runtime to perform an operation, providing a callback function and then moves on to do something else. When the operation has been completed, a message is enqueued along with the provided callback function. At some point in the future, the message is dequeued and the callback fired.
在js中,几乎所有的I/O都是非阻塞(译者注:简单的说就是在读文件或数据库时,程序不暂停等待结果,而是继续执行,等读到了结果后去执行)的.包括HTTP请求,数据库操作还是文件读写.线程执行了一个I/O操作时返回一个回调函数,然后继续执行其他程序.当那个操作完成时,一个携带着回调函数的(完成)消息被加到消息队列,在后来某个点,这个消息出队,相应的回调函数被执行.
While this interaction model may be familiar for developers already accustomed to working with user interfaces – where events like “mousedown,” and “click” could be triggered at any time – it’s dissimilar to the synchronous, request-response model typically found in server-side applications.
对于已经习惯了UI(用户界面)的程序员来说,这种交互模式再熟悉不过了,比如,你可以随时单击,双击. 这种模式和典型的同步的请求响应模式是不一样的.Let’s compare two bits of code that make HTTP requests to www.google.com and output the response to console. First, Ruby, with Faraday:
让我们来用一个小程序来请求一下www.google.com,并打印一下结果,比较一下. 首先,看下Ruby,用Faraday(译者注:应该是HTTP类库):
1 2 3 |
response = Faraday.get 'http://www.google.com'
puts response
puts 'Done!'
|
The execution path is easy to follow:
执行顺序如下:
- The get method is executed and the thread of execution waits until a response is received
- The response is received from Google and returned to the caller where it’s stored in a variable
- The value of the variable (in this case, our response) is output to the console
- The value “Done!” is output to the console
1,get方法被执行并且这个执行线程阻塞(wait)着直到服务端响应.
2,从google响应的信息保存到一个变量(response)中
3,这个变量(response)的内容输出到控制台
4,"Done!" 输出到了控制台
Let’s do the same in JavaScript with Node.js and the Request library:
来让我们看看node.js的例子:
1 2 3 4 5 |
request('http://www.google.com', function(error, response, body) {
console.log(body);
});
console.log('Done!');
|
A slightly different look, and very different behavior:
看起来稍有不同,然而本质却大不相同:
- The request function is executed, passing an anonymous function as a callback to execute when a response is available sometime in the future.
- “Done!” is immediately output to the console
- Sometime in the future, the response comes back and our callback is executed, outputting its body to the console
1.请求执行时,将一个回调函数作为参数传过去,这个回调函数将被执行当响应返回时.
2."Done!" 被输出到控制台
3.响应返回时,那个回调函数被执行,body被输出到控制台
The Event Loop(事件轮循)
The decoupling of the caller from the response allows for the JavaScript runtime to do other things while waiting for your asynchronous operation to complete and their callbacks to fire. But where in memory do these callbacks live – and in what order are they executed? What causes them to be called?
响应回调函数允许javascript异步去做其它的事情,当等待响应的时候.回调函数总是在异步操作完成时被触发.
JavaScript runtimes contain a message queue which stores a list of messages to be processed and their associated callback functions. These messages are queued in response to external events (such as a mouse being clicked or receiving the response to an HTTP request) given a callback function has been provided. If, for example a user were to click a button and no callback function was provided – no message would have been enqueued.
js运行时有一个消息队列和一个与之关联的回调函数.事件发生时(像点击鼠标),一个被提供回调函数的消息入队.如果一个用户点击了按钮,而这个按钮没有回调函数,那么就没有消息入队.
In a loop, the queue is polled for the next message (each poll referred to as a “tick”) and when a message is encountered, the callback for that message is executed.
事件轮循中,队列一直循环(每一次循环称为一个"tick"),当事件被触发时,回调函数被执行.
The calling of this callback function serves as the initial frame in the call stack, and due to JavaScript being single-threaded, further message polling and processing is halted pending the return of all calls on the stack. Subsequent (synchronous) function calls add new call frames to the stack (for example, function init calls function changeColor).
在调用栈中,这个回调函数的调用作为初始,并且由于javascript单线程,下一个的消息和主进程都会停止.
1 2 3 4 5 6 7 8 9 |
function init() {
var link = document.getElementById("foo");
link.addEventListener("click", function changeColor() {
this.style.color = "burlywood";
});
}
init();
|
In this example, a message (and callback, changeColor) is enqueued when the user clicks on the ‘foo’ element and an the “onclick” event fires. When the message is dequeued, its callback function changeColor is called. When changeColor returns (or an error is thrown), the event loop continues. As long as function changeColor exists, specified as the onclick callback for the ‘foo’ element, subsequent clicks on the element will cause more messages (and associated callback changeColor) to become enqueued.
这个例子中,当用户点击了‘foo'元素,消息(即改变颜色的那个回调函数)被入队。当消息出队的时候,改变颜色(changeColor)函数被执行。changeColor函数执行完返回(或抛出错误)了,事件论询继续执行。
Queuing Additional Messages
If a function called in your code is asynchronous (like setTimeout), the provided callback will ultimately be executed as part of a different queued message, on some future tick of the event loop. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function f() {
console.log("foo");
setTimeout(g, 0);
console.log("baz");
h();
}
function g() {
console.log("bar");
}
function h() {
console.log("blix");
}
f();
|
Due to the non-blocking nature of setTimeout, its callback will fire at least 0 milliseconds in the future and is not processed as part of this message. In this example, setTimeout is invoked, passing a callback function g and a timeout of 0 milliseconds. When the specified time elapses (in this case, almost instantly) a separate message will be enqueued containing g as its callback function. The resulting console activity would look like: “foo”, “baz”, “blix” and then on the next tick of the event loop: “bar”. If in the same call frame two calls are made to setTimeout – passing the same value for a second argument – their callbacks will be queued in the order of invocation.
Web Workers
Using Web Workers enables you to offload an expensive operation to a separate thread of execution, freeing up the main thread to do other things. The worker includes a separate message queue, event loop, and memory space independent from the original thread that instantiated it. Communication between the worker and the main thread is done via message passing, which looks very much like the traditional, evented code-examples we’ve already seen.
First, our worker:
1 2 3 4 5 6 7 |
// our worker, which does some CPU-intensive operation
var reportResult = function(e) {
pi = SomeLib.computePiToSpecifiedDecimals(e.data);
postMessage(pi);
};
onmessage = reportResult;
|
Then, the main chunk of code that lives in a script-tag in our HTML:
1 2 3 4 5 6 7 8 |
// our main code, in a <script>-tag in our HTML page
var piWorker = new Worker("pi_calculator.js");
var logResult = function(e) {
console.log("PI: " + e.data);
};
piWorker.addEventListener("message", logResult, false);
piWorker.postMessage(100000);
|
In this example, the main thread spawns a worker and registers the logResult callback function to the its “message” event. In the worker, the reportResult function is registered to its own “message” event. When the worker thread receives the message from the main thread, the worker enqueues a message and corresponding reportResult callback. When dequeued, a message is posted back to the main thread where a new message is enqueued (along with the logResult callback). In this way the developer can delegate CPU-intensive operations to a separate thread, freeing the main thread up to continue processing messages and handling events.
A Note on Closures
JavaScript’s support for closures allow you to register callbacks that, when executed, maintain access to the environment in which they were created even though the execution of the callback creates a new call stack entirely. This is particularly of interest knowing that our callbacks are called as part of a different message than the one in which they were created. Consider the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function changeHeaderDeferred() {
var header = document.getElementById("header");
setTimeout(function changeHeader() {
header.style.color = "red";
return false;
}, 100);
return false;
}
changeHeaderDeferred();
|
In this example, the changeHeaderDeferred function is executed which includes variable header. The function setTimeout is invoked, which causes a message (plus the changeHeader callback) to be added to the message queue approximately 100 milliseconds in the future. The changeHeaderDeferred function then returns false, ending the processing of the first message – but the header variable is still referenced via a closure and is not garbage collected. When the second message is processed (the changeHeader function) it maintains access to the header variable declared in the outer function’s scope. Once the second message (the changeHeader function) is processed, the header variable can be garbage collected.
Takeaways
JavaScript’s event-driven interaction model differs from the request-response model many programmers are accustomed to – but as you can see, it’s not rocket science. With a simple message queue and event loop, JavaScript enables a developer to build their system around a collection of asynchronously-fired callbacks, freeing the runtime to handle concurrent operations while waiting on external events to happen. However, this is but one approach to concurrency. In the second part of this article I’ll compare JavaScript’s concurrency model with those found in MRI Ruby (with threads and the GIL), EventMachine (Ruby), and Java (threads).
Additional Reading
- Check out this presentation I did recently, titled “The JavaScript Event Loop: Concurrency in the Language of the Web”
- Concurrency model and Event Loop @ MDN
- An intro to the Node.js platform, by Aaron Stannard
http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/
相关推荐
对NodeJs的事情轮询机造一孔之见。查阅了些许材料后,总算掀开了其神奇的里纱。下面这篇文章主要介绍了Node.JS中事件轮询(Event Loop)的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
// 获取对应分区的事件循环器 EventLoop eventLoop = eventLoopGroup.next(); // 将事件放入事件循环器的任务队列 eventLoop.submit(new EventTask(eventRecord)); }}上述代码中,`dispatch` 方法是分发器的核心,...
在编程领域,C#语言提供了一组强大的特性,如接口(Interface)、委托(Delegate)和事件(Event),这些是构建可扩展、灵活且易于维护的软件系统的关键元素。本篇文章将深入探讨这些概念以及如何利用它们实现事件...
在这个基于eventloop的Java实现中,我们看到几个核心概念和技术,包括EventLoop(事件循环)、无锁数据结构、最小堆定时器以及Pipeline机制。下面将详细阐述这些知识点。 1. EventLoop(事件循环): 事件循环是...
本文档是一份关于单片机程序架构的详解篇,特别是涉及时间片轮询的内容。通过单片机程序架构的概念,我们可以了解到单片机软件开发中对于程序流程、任务调度和资源管理等的组织方式。而在时间片轮询(又称时间分片或...
事件轮询(Event Loop)机制是Node.js高性能背后的核心,理解其工作原理对于开发高效Node.js应用至关重要。 事件轮询机制基于libuv库实现,libuv是Node.js的底层C库,负责Node.js的并发处理和事件轮询。libuv通过六...
通过本文的介绍,我们可以看出,“init.c轮询注册事件”这一概念在Linux内核初始化过程中扮演着极其重要的角色。它不仅有助于提高系统的启动效率,还能增强内核的稳定性和可靠性。对于从事操作系统开发或维护工作的...
### 单片机程序架构中的时间片轮询技术解析 #### 概述 单片机是一种集成微处理器、存储器及多种输入/输出接口于一体的微型计算机系统,在控制系统领域有着广泛的应用。随着技术的发展,单片机的应用场景越来越复杂...
在C#编程中,轮询算法是一种常用的策略,它用于在多个选项或资源之间均匀分配注意力或处理。在这个实例中,轮询算法被用来决定在用户访问页面时,一段特定的代码(例如曝光代码)是否按照预设的概率显示。下面我们将...
Mosquitto 源码新增轮询事件 在本文中,我们将基于 Mosquitto 源码新增轮询事件,并对其进行详细的分析和说明。 首先,让我们了解一下 Mosquitto 的基本概念。Mosquitto 是一个基于 MQTT 协议的开源消息代理服务器...
《S7-200SMART Modbus RTU轮询程序详解及使用指南》 在工业自动化领域,西门子的S7-200SMART系列PLC因其小巧、灵活、功能强大而广受青睐。本篇文章将深入探讨如何在S7-200SMART PLC上实现Modbus RTU协议的轮询程序...
《S7-200SMART Modbus轮询应用库详解》 S7-200SMART是西门子推出的一款小型PLC(可编程逻辑控制器),它在工业自动化领域广泛应用。Modbus则是一种开放的通信协议,允许不同设备之间进行数据交换。本篇文章将深入...
在Java编程中,轮询(Polling)是一种常见的技术,用于检查某个条件是否满足,或者等待特定事件的发生。它通常涉及在一个循环结构中定期检查某个状态,直到满足预设条件为止。例如,我们可以设计一个线程,该线程...
然而,在进行MODBUS通信轮询时,可能会遇到轮询卡死的情况,即PLC无法按照预设顺序执行通信任务。本文将详细介绍如何在S7-200SMART PLC中检测和处理轮询卡死问题,以及如何重新开启轮询。 首先,理解轮询机制是关键...
iOS 中效率极高的多 event 定时器,适用于复杂的轮询任务管理!.zip,A simple to use library that gets around a load of problems that arise when you start trying to do much inside 'loop'.
// Times eventloop delay, process info, memory info, etc. indy . whip ( { /* dispatcher: new StatsD() */ } ) ; // Not sure why you would need it yet but, lets just see. indy . pause ( ) ; indy . ...
原生jQuery库是一种广泛使用的JavaScript库,它简化了DOM操作、事件处理、动画效果和Ajax交互等任务。在“原生jQuery,轮询滚动消息代码”这个主题中,我们将深入探讨如何利用jQuery来实现一种常见的用户界面功能:...