Conceptually just having one queue for jobs seems to be sufficient for most use-cases.
What are the reasons for having multiple queues and distinguishing those into "microtasks" and (macro)"tasks"?
Having multiple (macro) task queues allows for task prioritization.
For instance, an User Agent (UA) can choose to prioritize a user event (e.g a click event) over a network event, even if the latter was actually registered by the system first, because the former is probably more visible to the user and requires lower latency.
In HTML this is allowed by the first step of the Event Loop's Processing model, which states that the UA must choose the next task to execute from one of its task queues.
(note: HTML specs do not require that there are multiple queues, but they do define multiple task sources to guarantee the execution order of similar tasks).
Now, why have yet an other beast called microtask queue?
We can find a few early discussions about "how" it should be implemented, but I didn't dig as far as to find who proposed this idea the first and for what use case.
However from the discussions I found, we can see that a few proposals needing such a mechanism were on the road:
Mutation Observers
Now-deprecated ES Object.observe()
At-that-time-incoming ES Promises,
Also-incoming-at-that-time-and-I-don't-know-why-it's-cited ES WeakRefs
HTML Custom Elements callback
The first two being also the first to be implemented in browsers, we can probably say that their use case was the main reason for implementing this new kind of queue.
Both actually did similar things: they listened for changes on an object (or the DOM tree), and coalesced all the changes that occurred during a job into a single event (not to be read as Event).
One could argue that this event could have been queued in the next (macro) task, with the highest priority, except that the Event Loop was already a bit complex, and every jobs did not necessarily come from tasks.
For instance, the rendering is actually a part of every Event Loop iteration, except that most of the time it early exits because it wasn't the time to render.
So if you do your DOM modifications during a rendering frame, you could have the modifications rendered, and only after the whole rendering took place you'd get the callback.
Since the main use case for observers is to act on the observed changes before they trigger performance heavy side-effects, I guess you can see how it was necessary to have a mean to insert our callback right after the job which has done the modifications.
PS: At that time I didn't know much about the Event Loop, I was far from specs matters and I may have put some anachronisms in this answer.
Related
It is to my understanding that the node event loop will continue to handle requests until the event loop is empty, at which point it will look the the event queue to complete the blocking I/O requests.
My question is.. What happens if the event loop never becomes empty? Not due to bad code (i.e. never ending loop) but due to consistent client requests (thinking something like google that gets never ending requests)?
I realize there is a possibility I am misunderstanding a fundamental aspect of how client requests are handled by a server.
There are actually several different phases of the event loop (timers, I/O, check events, pending callbacks, etc...) and they are checked in a circular order. In addition some things (like promises and other microtasks) go to the front of the line no matter what phase of the event loop is currently processing.
It is possible that a never ending set of one type of event can block the event queue from serving other types of events. That would be a design/implementation problem that needs to be prevented.
You can read a bit more about the different types of things in the event loop here: https://developer.ibm.com/tutorials/learn-nodejs-the-event-loop/ and https://www.geeksforgeeks.org/node-js-event-loop/ and https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io.
While it is possible to overload the event loop such that is doesn't get out of one phase of the event loop, it's not very common because of the way most processing in nodejs consists of multiple events which gives other things a chance to interleave. For example, processing an incoming http request consists of connecting, reading, writing, closing, etc... and the processing of that event may involve other types of events. So, it can happen that you overload one type of event (I've done it only once in my coding and that was because of poorly written communication between the main thread and a bunch of WorkerThreads that was easily fixed to not have the problem once I realized what the problem was.
It feels like this must be a stupid question but I don't understand something fundamental about making a request in indexedDB.
Why are the requests made before the event handlers are defined? For example, the request = objectStore.add(data) is made before the request.onsuccess and request.onerror functions are declared. Is this correct? Is it possible that the request could complete before the event handlers are registered?
I'm comparing it to the creation of an image element, followed by declaring the event handlers for onload and onerror, all before setting the source attribute to the location of a file and attempting to load it. But a request "element" can't be created before a request is made; so, there's nothing to attach the events to until the request has already been made.
Please let me know what I'm missing here. I've been writing and retrieving data from indexedDB without issue, and think I've been coding it correctly; but I want to make sure this is right and will always work.
Thank you.
Duplicate Response
I read that question and answer sometime ago, when I first started reading about indexedDB, and completely forgot about it. Had I found it again before I wrote this question, I likely would not have submitted it and would've just accepted that the code ought to work out whether I understand it or not. Handling error events and transaction aborts is what got me to thinking about the statement order again.
However, after reading the answer again, I don't understand enough to be any further along than just accepting it and hoping it will always work. I'm not trying to be snide. In one sense, it is just confusing for my limited capacity to think of event loops and epochs and that everything sort of happens all at once.
At the end of the epoch (or the start of the next, whatever you think is easier to understand), the underlying JS engine goes back and looks at what is registered to execute, and then executes everything nearly all at once.
There has to be an order of execution or nothing makes sense, asynchronous or not. I understand that the interpreter does not wait for an anysnchronous process to complete before starting to execute the next line of code. But aren't the synchronous statements processed completely in turn in the order they appear in the code and the asynchronous ones started off in the order they appear in the code, such that if an asynchronous process errored quickly, the event could be missed if the event handlers weren't declared in advance? The event handlers are not hoisted like function declarations, are they? This is the part that I still find confusing.
In this article by Jake Archibald on promises, in the introduction he presents an example concerning the loading of images and writes:
Unfortunately, in the example above, it's possible that the events happened before we started listening for them, so we need to work around that using the "complete" property of images.
and
This doesn't catch images that error'd before we got a chance to listen for them; unfortunately the DOM doesn't give us a way to do that. Also, this is loading one image, things get even more complex if we want to know when a set of images have loaded.
That gives the impression that order is important, such that, in the case of images, when possible, the source should be assigned after declaring all the event handlers, in order to not miss hearing events. The important part for me was the fact that an event could take place before the event handler was declared/registered.
I tried to follow the same pattern of making the request after declaring the event handlers in indexedDB, but it doesn't appear possible because there's nothing to attach the events to until the request is made.
Even when all the statements are asynchronous, such as in this example in the MDN Web Docs on Using IndexedDB, some things are still rather confusing. The objectStore.transaction.oncomplete is an interesting statement. We're waiting for the objectStore to be created before attempting to write data to it. (I think that's considered bad practice, writing data in an onupgradeneeded event; so, we don't use that statement.) But what is confusing is why we don't worry about the objectStore being created before creating an index in it. Why isn't the createIndex statement started at the same time the createObjectStore statement started, if everything is processed all at once? If the createObjectStore statement doesn't complete before the createIndex statement begins, shouldn't an event handler be required or it would fail because objectStore wouldn't yet exist?
I know it works because I've been using the same code pattern, but I really don't understand it.
These two items--the potential to miss events and why an event handler isn't needed in this indexedDB example--are what I'd like to better understand. I don't know if this makes my question different or not, but the answer to the duplicate question doesn't answer these for me. Perhaps, I'd have to understand the JS engine better to understand the answer to these questions.
const dbName = "the_name";
var request = indexedDB.open(dbName, 2);
request.onerror = function(event) {
// Handle errors.
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
// Create an objectStore to hold information about our customers. We're
// going to use "ssn" as our key path because it's guaranteed to be
// unique - or at least that's what I was told during the kickoff meeting.
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
// Create an index to search customers by name. We may have duplicates
// so we can't use a unique index.
objectStore.createIndex("name", "name", { unique: false });
// Create an index to search customers by email. We want to ensure that
// no two customers have the same email, so use a unique index.
objectStore.createIndex("email", "email", { unique: true });
// Use transaction oncomplete to make sure the objectStore creation is
// finished before adding data into it.
objectStore.transaction.oncomplete = function(event) {
// Store values in the newly created objectStore.
var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
customerData.forEach(function(customer) {
customerObjectStore.add(customer);
});
};
};
Clarification/Response to Answer/Comments
Thank you for taking the time to respond to my question and for the added explanation.
First, by "before" I simply mean the order in which the statements appear in the script.
I think I follow your analogy and it is a good one. I'm just still not clear on why the employees never submit their work to the secretary until the next day, when it is guaranteed that a secretary will be there to receive it.
It sounds similar to the fact that the javascript interpreter, when it performs its equivalent of compiling the script, hoists the function declarations, such that a function can be invoked in the code before the function declaration has been made.
It would appear that your saying, restated in my simple terms, that the JS engine, at some point before final execution, assigns the event handlers (the secretaries) to be registered in an earlier epoch than the one in which the requests (the employees) that utlimately trigger the events will complete. Therefore, it doesn't matter where in the code the request statements appear relative to the event handlers, that is, as long as they are defined within the same epoch.
The JS engine doesn't know when the request will complete, but only when the event handlers have been registered to start listening and when the request has commenced. As long as the JS engine has a process to order these steps properly independent of the order the statements appear in the code, such that an event cannot be missed, then it's no different to me than the hoisting of function declarations and I don't really have to think much about it any longer to accomplish my tasks.
However, I would still like to better understand what an epoch is, at least in terms of knowing that the statements are made within the same epoch. I don't see any mention of an epoch in the article on "Concurrecny Model and Event Loop" in MDN Web Docs. Would you mind pointing me to any good resources you know of?
Thank you.
Final Notes
I came across these two items through a link, here, on stack overflow. This same question was asked eight years ago and was answered about the same way but with different terminology; that is, instead of epochs, it was that javascript code will "run to completion" or has run-to-completion semantics. This question refers you to this document which can be searched for "run to completion" to read two exchanges about why there is not a race condition in this set up of making a request before registering the event handlers. The older JavaScript book I have by David Flanagan, in discussing the execution of JS "programs," states that, because JS has single-threaded execution, one never has to worry about race conditions; but I don't know if he was referring to this situation exactly.
Thus, this question has been asked and answered multiple times in the past and I guess I'm just another newbie asking an old question as if I was the first to have thought of it, and without enough knowledge of how JS processes.
The article "Concurrency Model and Event Loop", linked above, has a brief "Run-to-completion" section; but I didn't understand its implications until after reading the last document linked above.
I now take it to mean that all the code in a function will run to completion before any other code can begin, which seems to have two interpretations.
One is that the asynchronous request on the database is queued when the statement is reached in the function code but won't actually commence until all the other statements in the function are run, including the event handlers that are declared afterward.
Or, according to that last linked document above, the asynchronous request may run and even complete before the event handlers are registered, but the notification of its completion will remain in the queue and won't execute until after the rest of the statements in the function are run and the event handlers have been registered.
Interpretation 2 appears to be the accurate one but, whichever is the actual case, it all makes adequate sense to me now and explains why the secretary will always be there before the employee submits the work, and why, even if the employee finishes the work in a nanosecond, the employee won't submit the work until the next day when a secretary is guaranteed to be present to receive it. The employee may place the work-complete notification in the queue, but the queue won't sound the notification for a secretary to hear until the next day.
Thanks, Josh, for the additional explanation about what is meant by epochs and how that terminology works out in operation. I accepted your answer and appreciate you taking the time to write it all out.
Now that I seem to understand why the event-handler declarations can be made later in the code than the making of the request, I still don't understand why we can create an object store and then immediately create an index on that object store without having to wait until we know the object store has been successfully created, unless it is synchronous or something else special takes place in a versionchange transaction / onupgradeneeded event. There are no events mentioned in the MDN Web Docs description of createObjectStore and no examples that have any listeners on it; so; I'll just assume it's never necessary.
Thanks again.
Why are the requests made before the event handlers are defined?
It does not matter.
For example, the request = objectStore.add(data) is made before the request.onsuccess and request.onerror functions are declared. Is this correct?
Yes it is correct because again it does not matter.
I would be careful about your use of the word before. Maybe it means something different to me than it does to you. I can't tell. But maybe this is what is tripping you up.
Is it possible that the request could complete before the event handlers are registered?
If you register the event handlers in the same epoch as when you make the request, then no. The request only completes in a later epoch.
Ok, here is my attempt at explaining by example (sorry if this is bad!). Personification is generally a good educational technique, and is less intimidating then using raw technical terms, so let's go with that.
Let's say you are a boss, and have employees. Let's say you ask an employee to do some work for you. Then, you ask that employee to report back to your secretary when they completed the work. Immediately after asking the employee to go do that other work, you carry on doing your own work, without waiting for that employee to finish their work and report back. You are both basically doing work at the same time.
Now, in this situation, what happens if you don't have a secretary at the time you hand the employee a request to do something? Well, no problem. You go and hire another secretary before that employee finishes their work and before that employee even knows who to report back to, which is fine because all the employee knows is they report to your secretary. The employee does not know whether your secretary exists or not at the time of being assigned work, and does not need to know that. The missing secretary did not prevent that employee from getting started, or understanding the work to be done. And by the time that employee completes their work, you have a secretary ready and waiting. Or, you don't, because you don't happen to care to even acknowledge whether the work was actually completed, you just made a command and trusted the employee to do their job, whatever. You really only care about having them report back to your secretary if you need to do some other work that has to wait until after the first project is done, and that is a different concern.
Let's pretend you already had a secretary at the time you assigned the employee the work. What is the difference between this situation of already having a secretary, and the situation where you go and hire one shortly after assigning the work, but before it is done? There is NO difference.
Now, let's try and really address your concern. What you're suggesting is that it seems impossible to reliably go out and hire that secretary before you know whether the employee finished their assignment. This I think is the critical misunderstanding. It is entirely possible to do this. Why is that? I suppose it is not the easiest thing to grasp.
I am going to stretch this metaphor a bit and impose a strange rule. No matter how simple the project you hand off to the employee, even if it is just to run and get you coffee in the morning, they will never ever get back to you the same day. They will always finish their work some later day, at the earliest tomorrow. They might even finish their work within one fleeting nanosecond of you telling them, but they will NEVER get back to you or your secretary right away, they will always be delayed until tomorrow at the earliest.
This means you have all day to go and hire that secretary that did not exist at the time you gave the employee the order. So long as you do it before tomorrow, you're good. That secretary will exist and be working for you by the time the employee responds tomorrow, and will be able to receive the message from the employee.
Edit response to your added comments:
Yep, hoisting is similar in many respects. Things can happen in a different order then written in code. Hoisting is of course synchronous so it is not a perfect similarity, but the out-of-order aspect is still similar.
Epoch is just my own word i use for a single iteration of the event loop. Like in the case of a for loop using i for i from 0 to 2, there are 3 epochs, iteration 0, iteration 1, and iteration 2. i just call them epochs because it is like categories of time.
In a promise case, it might even be a microtask. In a js worker case, it might be thread-like (and workers are the new hotness over the old child-iframe technique). Basically these are all just ways to 'realize' doing more than one thing at a time. Node calls it a tick, and has things like nextTick() that defers code execution until the next tick of its loop. Within a single epoch, things happen in the order they are written (and notably hoisting is all in epoch 0). but some code may be async, and therefore happen across epochs, and therefore may run in a different order than it was written. Code written earlier may happen in a later epoch.
When you make a request, it says, start doing this thing, and get back to me at the earliest in the next epoch. You have up until the end of the current epoch to register handlers for the request.
Some code, like in the case of an image preloader as mentioned in your example, has to take into account that it attaches the listeners too late (images are being preloaded in an alternate timeline and some may already be loaded and in some browsers this means load will not fire), so it wants to check imageElement.complete to catch that case. In other cases of event listener implementations, some dispatcher implementations will fire events to newly added listeners for events that already happened where the new listener was not listening at the time of the event. But that is not a universal characteristic of event listener implementations, just a characteristic of certain implementations.
And in the case of the transaction.oncomplete thing from within onupgradeneeded, that is just not a great example. It is doing stuff it does not need to do.
This is the technical answer to your question:
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
The JS concurrency model is cooperative with "run-to-completion" semantics (no parallel processing of events in the same queue). This means that any async response will be posted as a message to the window event loop, and all the sequential code you see after the request is guaranteed to execute before the async response processing starts.
That said, from usability point of view, the IndexDB API is not affording the intent in the most expressive manner, and coming from other languages with preemptive threading you are excused for being confused :-)
From what I see, if an event in Node take a "long time" to be dispatched, Node creates some kind of "queue of events", and they are triggered as soon as possible, one by one.
How long can this queue be?
While this may seem like a simple question, it is actually a rather complex problem; unfortunately, there's no simple number that anyone can give you.
First: wall time doesn't really play a part in anything here. All events are dispatched in the same fashion, whether or not things are taking "a long time." In other words, all events pass through a "queue."
Second: there is no single queue. There are many places where different kinds of events can be dispatched into JS. (The following assumes you know what a tick is.)
There are the things you (or the libraries you use) pass to process.nextTick(). They are called at the end of the current tick until the nextTick queue is empty.
There are the things you (or the libraries you use) pass to setImmediate(). They are called at the start of the next tick. (This means that nextTick tasks can add things to the current tick indefinitely, preventing other operations from happening whereas setImmediate tasks can only add things to the queue for the next tick.)
I/O events are handled by libuv via epoll/kqueue/IOCP on Linux/Mac/Windows respectively. When the OS notifies libuv that I/O has happened, it in turn invokes the appropriate handler in JS. A given tick of the event loop may process zero or more I/O events; if a tick takes a long time, I/O events will queue in an operating system queue.
Signals sent by the OS.
Native code (C/C++) executed on a separate thread may invoke JS functions. This is usually accomplished through the libuv work queue.
Since there are many places where work may be queued, it is not easy to answer "how many items are currently queued", much less what the absolute limit of those queues are. Essentially, the hard limit for the size of your task queues is available RAM.
In practice, your app will:
Hit V8 heap constraints
For I/O, max out the number of allowable open file descriptors.
...well before the size of any queue becomes problematic.
If you're just interested in whether or not your app under heavy load, toobusy may be of interest -- it times each tick of the event loop to determine whether or not your app is spending an unusual amount of time processing each tick (which may indicate that your task queues are very large).
Handlers for a specific event are called synchronously (in the order they were added) as soon as the event is emitted, they are not delayed at all.
The total number of event handlers is limited only by v8 and/or the amount of available RAM.
I believe you're talking about operations that can take an undefined amount of time to complete, such as an http request or filesystem access.
Node gives you a method to complete these types of operations asynchronously, meaning that you can tell node, or a 3rd party library, to start an operation, and then call some code (a function that you define) to inform you when the operation is complete. This can be done through event listeners, or callback functions, both of which have their own limitations.
With event listeners the maximum amount of listeners you can have is dependent on the maximum array size of your environment. In the case of node.js the javascript engine is v8, but according to this post there is a maximum set out by the 5th ECMA standard of ~4billion elements, which is a limit that you shouldn't ever overcome.
With callbacks the limitation you have is the max call stack size, meaning how deep your functions can call each other. For instance you can have a callback calling a callback calling a callback calling another callback, etc etc. The call stack size dictates how may callbacks calling callbacks you can have. Note that the call stack size can be a limitation with event listeners as well as they're essentially callbacks that can be executed multiple times.
And these are the limitations with each.
I'm a little bit confused about how browsers handle JavaScript events.
Let's say I have two event handlers attached to buttons A and B. Both event handlers take exactly the same time to complete. If I click on button A first and button B next, is it true that the event handler for the button A is always executed first (because the event loop is a FIFO queue), but when they finish is completely unpredictable? If so, what actually determines this order?
Yes. The order of event handlers executed is guaranteed, and in practice they will not overlap.
This is the beauty of the event loop as a concurrency model. You don't have to think about threading issues like deadlocks, livelocks and race conditions most of the time (though not always).
Order of execution is simple and JavaScript in the browser is single threaded most of the time and in practice you do not have to worry about order of execution of things.
However the fact order of mouse events is guaranteed has hardly anything has to do with JavaScript. This is not a part of the JavaScript language but a part of something called the DOM API, the DOM (document object model) is how JavaScript interacts with your browser and the HTML you write.
Things called Host Objects are defined in the JavaScript specification as external objects JS in the browser works with, and their behavior in this case is specified in the DOM API.
Whether or not the order DOM events are registered is guaranteed is not a part of JavaScript but a part of that API. More specifically, it is defined right here. So to your question: Yes, order of event execution is certain except for control keys (like (control alt delete)) which can mess order of evaluation up.
The Javascript engine is single threaded. All of your event handlers happen sequentially; the click handler for A will be called, and finish before the handler for B ever starts. You can see this by sleep()ing in one handler, and verifying that the second handler will not start until the first has finished.
Note that setTimeout is not valid for this test, because it essentially registers a function with the engine to be called back at a later time. setTimeout returns immediately.
This fiddle should demonstrate this behavior.
Well the commands are indeed in a FIFO when executed by javascript. However, the handlers may take different amounts of time to send you the result. In this case the response from handler B may come back earlier and response from handler A may come later.
I have seen this link: Implementing Mutual Exclusion in JavaScript.
On the other hand, I have read that there are no threads in javascript, but what exactly does that mean?
When events occur, where in the code can they interrupt?
And if there are no threads in JS, do I need to use mutexes in JS or not?
Specifically, I am wondering about the effects of using functions called by setTimeout() and XmlHttpRequest's onreadystatechange on globally accessible variables.
Javascript is defined as a reentrant language which means there is no threading exposed to the user, there may be threads in the implementation. Functions like setTimeout() and asynchronous callbacks need to wait for the script engine to sleep before they're able to run.
That means that everything that happens in an event must be finished before the next event will be processed.
That being said, you may need a mutex if your code does something where it expects a value not to change between when the asynchronous event was fired and when the callback was called.
For example if you have a data structure where you click one button and it sends an XmlHttpRequest which calls a callback the changes the data structure in a destructive way, and you have another button that changes the same data structure directly, between when the event was fired and when the call back was executed the user could have clicked and updated the data structure before the callback which could then lose the value.
While you could create a race condition like that it's very easy to prevent that in your code since each function will be atomic. It would be a lot of work and take some odd coding patterns to create the race condition in fact.
The answers to this question are a bit outdated though correct at the time they were given. And still correct if looking at a client-side javascript application that does NOT use webworkers.
Articles on web-workers:
multithreading in javascript using webworkers
Mozilla on webworkers
This clearly shows that javascript via web-workers has multithreading capabilities. As concerning to the question are mutexes needed in javascript? I am unsure of this. But this stackoverflow post seems relevant:
Mutual Exclusion for N Asynchronous Threads
Yes, mutexes can be required in Javascript when accessing resources that are shared between tabs/windows, like localStorage.
For example, if a user has two tabs open, simple code like the following is unsafe:
function appendToList(item) {
var list = localStorage["myKey"];
if (list) {
list += "," + item;
}
else {
list = item;
}
localStorage["myKey"] = list;
}
Between the time that the localStorage item is 'got' and 'set', another tab could have modified the value. It's generally unlikely, but possible - you'd need to judge for yourself the likelihood and risk associated with any contention in your particular circumstances.
See the following articles for a more detail:
Wait, Don't Touch That: Mutual Exclusion Locks & JavaScript - Medium Engineering
JavaScript concurrency and locking the HTML5 localStorage - Benjamin Dumke-von der Eh, Stackoverflow
As #william points out,
you may need a mutex if your code does something where it expects a
value not to change between when the asynchronous event was fired and
when the callback was called.
This can be generalised further - if your code does something where it expects exclusive control of a resource until an asynchronous request resolves, you may need a mutex.
A simple example is where you have a button that fires an ajax call to create a record in the back end. You might need a bit of code to protect you from trigger happy users clicking away and thereby creating multiple records. there are a number of approaches to this problem (e.g. disable the button, enable on ajax success). You could also use a simple lock:
var save_lock = false;
$('#save_button').click(function(){
if(!save_lock){
//lock
save_lock=true;
$.ajax({
success:function()
//unlock
save_lock = false;
}
});
}
}
I'm not sure if that's the best approach and I would be interested to see how others handle mutual exclusion in javascript, but as far as i'm aware that's a simple mutex and it is handy.
JavaScript is single threaded... though Chrome may be a new beast (I think it is also single threaded, but each tab has it's own JavaScript thread... I haven't looked into it in detail, so don't quote me there).
However, one thing you DO need to worry about is how your JavaScript will handle multiple ajax requests coming back in not the same order you send them. So, all you really need to worry about is make sure your ajax calls are handled in a way that they won't step on eachother's feet if the results come back in a different order than you sent them.
This goes for timeouts too...
When JavaScript grows multithreading, then maybe worry about mutexes and the like....
JavaScript, the language, can be as multithreaded as you want, but browser embeddings of the javascript engine only runs one callback (onload, onfocus, <script>, etc...) at a time (per tab, presumably). William's suggestion of using a Mutex for changes between registering and receiving a callback should not be taken too literally because of this, as you wouldn't want to block in the intervening callback since the callback that will unlock it will be blocked behind the current callback! (Wow, English sucks for talking about threading.) In this case, you probably want to do something along the lines of redispatching the current event if a flag is set, either literally or with the likes of setTimeout().
If you are using a different embedding of JS, and that executes multiple threads at once, it can get a bit more dicey, but due to the way JS can use callbacks so easily and locks objects on property access explicit locking is not nearly as necessary. However, I would be surprised if an embedding designed for general code (eg, game scripting) that used multi threading didn't also give some explicit locking primitives as well.
Sorry for the wall of text!
Events are signaled, but JavaScript execution is still single-threaded.
My understanding is that when event is signaled the engine stops what it is executing at the moment to run event handler. After the handler is finished, script execution is resumed. If event handler changed some shared variables then resumed code will see these changes appearing "out of the blue".
If you want to "protect" shared data, simple boolean flag should be sufficient.