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)的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
接口,事件,委托,驱动监听 1.面向接口的驱动监听 2.面向委托的驱动监听 3.事件驱动监听 4.事件轮询 5.事件异步轮询
init.c轮询注册事件init.c轮询注册事件init.c轮询注册事件init.c轮询注册事件
19年找到的这份资料,找到的过程异常艰辛,是一位老工程师总结的,我又加了一些注解,希望能帮助到大家~
单片机 程序架构 时间片轮询.
概括事件轮询机制:分为六个阶段 1.timers 定时器阶段 计时和执行到点的定时器回调函数 2.pending callbacks 某些系统操作(例如TCP错误类型) 3.idle,prepare 4.poll轮询阶段(轮询队列) 如果轮询队列不为空,...
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 . ...
S7-200SMART_modbusRTU轮询程序(读轮询+写优先)附使用说明
图片轮询查看
主要介绍了c# 实现轮询算法实例代码的相关资料,这里附有实例代码,具有一定的参考价值,需要的朋友可以参考下
本文实例为大家分享了Android实现轮询的方式,供大家参考,具体内容如下 1.通过rxjava实现(代码中使用了Lambda表达式) private static final int PERIOD = 10 * 1000; private static final int DELAY = 100; ...
松下plc 轮询 标准 程序 程式,基于松下plc做的485通讯和几台其他设备通信
S7-200SMART PLC进行MODBUS通信轮询卡死时重新开启轮询的具体方法演示
主要给大家介绍了关于在linux下日志定时轮询流程的相关资料,文中介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
144字节的轻量级JavaScript轮询库! 该库可用于以指定的时间间隔设置运行功能,也可用于轮询服务器以获取新数据。 该库的优点之一是避免setInterval不良行为,即函数间隔不要在启动之前等待前一个函数完成,否则会...
8通道输入,1通道输出,采取请求应答方式,对8通道输入进行仲裁,采用公平轮询机制;
基于mosquitto源码新增轮询事件