Given the following script:
console.log("start of hard script");
const start = performance.now();
setTimeout(() => console.log('setTimeout'),0);
document.addEventListener("DOMContentLoaded", () => {
console.log('fired event DOMContentLoaded')
});
document.addEventListener("click" , () => {
console.log("fired event click")
});
while(start + 1000 > performance.now());
console.log("end of hard script")
I am sure I read somewhere that the user interaction queue will be more prioritized than the timer queue.
I wanted to see how that priority is defined, but saw in the specs:
Let taskQueue be one of the event loop's task queues, chosen in an
implementation-defined manner, with the constraint that the chosen
task queue must contain at least one runnable task. If there is no
such task queue, then jump to the microtasks step below.
If WHATWG doesn't define a concrete order of queues, I'd like to know by what criteria implementors run? How do they evaluate the "important-ness" of queues? And in the end I'd like to see an example that shows the order of these queues, if it is possible.
I'd like to know by what criteria implementors run? How do they evaluate the "important-ness" of queues?
That's basically their call, a design choice made from years of experience looking at how their tool is being used and what should be prioritized (and also probably a good part of common sense).
The WHATWG indeed doesn't define at all how this task prioritization should be implemented. All they do is to define various task-sources (not even task-queues), to ensure that two tasks queued in the same source will get executed in the correct order.
The closest we have of defining some sort of prioritization is the incoming Prioritized Task Scheduling API which will give us, web-authors, the mean to post prioritized tasks, with three priority levels: "user-blocking", "user-visible" and "background".
To check what browsers actually implement, you'd have to go through their source-code and inspect it thoroughly.
I myself already spent a couple hours in there and all I can tell you about it is that you better be motivated if you want to get the full picture.
A few points that may interest you:
All browsers don't expose the same behavior at all.
In Chrome, setTimeout() still has a minimum delay of 1ms (https://crbug.com/402694)
In Firefox, because Chrome's 1ms delay was producing different results on some web-pages, they create a special very-low-priority task-queue only for the timers scheduled before the page load, the ones scheduled after are queued in a normal priority task-queue. (https://bugzil.la/1270059)
At least in Chrome, each task-queue has a "starvation" protection, which prevents said queue to flood the event-loop with its own task, by letting the queues with lower priority also execute some of their tasks, after some time (not sure how much).
And in the end I'd like to see an example that shows the order of these queues, if it is possible.
As hinted before, that's quite complicated, since there is no "one" order.
Your own example though is quite a good test, which in my Chrome browser does show correctly that the UI task-queue has an higher priority than the timer one (the while loop takes care of the 1ms minimum delay I talked about). But for the DOMContentLoaded though, I must admit I'm not entirely sure it shows anything significant: The HTML parser is also blocked by the while loop and thus the task to fire the event will only get posted after the whole script is executed.
But given this task is posted on the DOM Manipulation task source, we can check it's priority by forcing a other task that uses this task source, e.g script.onerror.
So here is an update to your snippet, with a few more task sources, called in reverse order of what my Chrome's prioritization seems to be:
const queueOnDOMManipulationTaskSource = (cb) => {
const script = document.createElement("script");
script.onerror = (evt) => {
script.remove();
cb();
};
script.src = "";
document.head.append(script);
};
const queueOnTimerTaskSource = (cb) => {
setTimeout(cb, 0);
}
const queueOnMessageTaskSource = (cb) => {
const { port1, port2 } = new MessageChannel();
port1.onmessage = (evt) => {
port1.close();
cb();
};
port2.postMessage("");
};
const queueOnHistoryTraversalTaskSource = (cb) => {
history.pushState("", "", location.href);
addEventListener("popstate", (evt) => {
cb();
}, { once: true });
history.back();
}
const queueOnNetworkingTaskSource = (cb) => {
const link = document.createElement("link");
link.onerror = (evt) => {
link.remove();
cb();
};
link.href = ".foo";
link.rel = "stylesheet";
document.head.append(link);
};
const makeCB = (log) => () => console.log(log);
console.log("The page will freeze for 3 seconds, try to click on this frame to queue an UI task");
// let the message show
setTimeout(() => {
window.scheduler?.postTask(makeCB("queueTask background"), {
priority: "background"
});
queueOnHistoryTraversalTaskSource(makeCB("History Traversal"));
queueOnNetworkingTaskSource(makeCB("Networking"));
queueOnTimerTaskSource(makeCB("Timer"));
// the next three are a tie in current Chrome
queueOnMessageTaskSource(makeCB("Message"));
window.scheduler?.postTask(makeCB("queueTask user-visible"), {
priority: "user-visible"
});
queueOnDOMManipulationTaskSource(makeCB("DOM Manipulation"));
window.scheduler?.postTask(makeCB("queueTask user-blocking with delay"), {
priority: "user-blocking",
delay: 1
});
window.scheduler?.postTask(makeCB("queueTask user-blocking"), {
priority: "user-blocking"
});
document.addEventListener("click", makeCB("UI task source"), {
once: true
});
const start = performance.now();
while (start + 3000 > performance.now());
}, 1000);
Related
So in this post , the macrotask queue seems to include movemouse. But then with this code
<html>
<body>
<button id="but_one" onclick='button_one()'>Button 1</button>
<button id="but_two" onclick='button_two()'>Button 2</button>
</body>
<script>
//document.addEventListener('mousemove', e => console.log("mouse move"));
function button_one() {
console.log("button one")
}
function button_two() {
console.log("button two before")
setTimeout(() => console.log("timeout : before sleep"), 0)
autoResolve().then(msg => console.log(".then before sleep " + msg));
sleep(5000);
autoResolve().then(msg => {
sleep(2000);
console.log(".then after sleep " + msg)
});
setTimeout(() => console.log("timeout : after sleep"), 0)
console.log("button two after")
}
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
async function autoResolve() { return "resolved" }
</script>
</html>
If you click button 2 and then 1, you can see that both promises (microtasks) are executed before button 1 click is registered, which makes sense to me. However both timeouts (macrotasks) seem to occur after, even the one that was queued before I clicked. This to me suggests that the listener has it's own 3rd queue but no source says this.
The same is observed with logging mousemove, however I removed that for the purposes of the console in the code-snippet.
Why does this happen?
edit: So this was done in Chrome Version 83.0.4103.61 on Windows 10 PC
The specification says that the event loop may have multiple task queues. Here's step 1 of the processing model:
Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner, with the constraint that the chosen task queue must contain at least one runnable task. If there is no such task queue, then jump to the microtasks step below.
(my emphasis)
It's not surprising that a browser might (conceptually) have events and timers in separate task queues in order to prioritize an event over a timer callback, perhaps especially when that event has been delayed because the JavaScript thread was too busy to handle it earlier.
I don't think it's specified any more ...er... specifically than that that "implementation-defined manner." I get the behavior you describe (the "button one" event before the timer callback) with Chrome, and I get the timer callbacks before the event with Firefox.
The macrotasks to run come from multiple so-called task sources. The browser is free to decide in what order to serve them, using multiple queues, as long as the events of each source occur in sequence. Event listeners and timeouts are different sources, and apparently your browser deems the click event more important.
Curious to see whether setTimeout() will be fired up asynchronously, I tried the following test script:
function timedText() {
var x = document.getElementById("txt");
setTimeout(function() {
x.value = "1 second"
}, 1000);
setTimeout(function() {
x.value = "2 seconds"
}, 2000);
setTimeout(function() {
x.value = "3 seconds"
}, 3000);
while (true) {}
}
<p>Click on the button below. The input field will tell you when two, four, and six seconds have passed.</p>
<button onclick="timedText()">Display timed text</button>
<input type="text" id="txt">
Sure enough, clicking the button causes the browser to hang.
This tells me that setTimeout() does not run on a separate thread.
But on a recent interview, the interviewer suggested otherwise... Does that mean that setTimeout() is browser/implementation dependent?
JavaScript is not multi threaded. Well there are WebWorkers that run in a different thread, but that's not really multi threading, more like multiple processes, that communicate with each other.
As of that the while (true) {} will block the js context, because it is an endless loop.
The setTimeout will register a function for later execution. But at no time code will run in parallel for the same context.
A while (true) itself does not necessarily create a blocking loop:
async function sleep(time) {
return new Promise((resolve, _) => setTimeout(resolve, time))
}
async function test(val) {
while (true) {
console.log('in while loop ' + val)
await sleep(1000)
}
}
test('foo')
test('bar')
So you can say with await/async you can create some kind of cooperative multitasking like setup, but still no multi threading
There is no thread in javascript.
setTimeout push just the delegate function insto a stack that will pop for the next pass.
You can read that JavaScript and Threads
This tells me that setTimeout() does not run on a separate thread.
Yes. There is only one thread in JS.
But on a recent interview, the interviewer suggested otherwise... Does
that mean that setTimeout() is browser/implementation dependent?
As far as i know only engine changed from browser to browser. Internal mechanism stands the same - event-loop processor.
When you call setTimeout() typically control is passing back into the host environment (the browser or native node.js code for example). What is happening then is that your callback is being registered in a list of timers to execute in the future. setTimeout() will the return back to your code which will continue executing.
When your script finally completes, control again will return to the host environment which has an event loop, this loop keeps spinning until it's finally time to call your registered callback.
You can actually approximate something like this in JavaScript itself by implementing an event loop just for fun:
class EventLoop {
constructor() {
this.entries = []; // a list of all registered callbacks
this.turns = 0; // keep track of how many turns of the loop we make
}
// Adds a new callback to the list
schedule(callback, condition) {
this.entries.push([condition, callback]);
}
// To removes a callback when it's been called
remove(entry) {
this.entries.splice(this.entries.indexOf(entry), 1);
}
// Run the loop until all registered callbacks were called
// Returns the number of turns it made in the while loop
run(timeout) {
this.turns = 0;
while (this.entries.length) {
for (const entry of this.entries) {
const [condition, callback] = entry;
if (condition()) {
callback();
this.remove(entry);
}
}
this.turns++;
}
return this.turns;
}
}
We can use this EventLoop to implement something like a setTimeout():
// Define a handy log function
const log = ((startTime) => (text) => {
console.log(`t+${(Date.now() - startTime).toFixed(3)}ms: ${text}`);
})(Date.now());
// Create an event loop
const loop = new EventLoop();
// Define a setTimeout using the event loop
const defer = (fn, timeout) => {
const start = Date.now();
const end = start + timeout;
loop.schedule(fn, () => Date.now() >= end);
};
// Schedule some nested events
defer(() => {
log('I run second');
defer(() => {
log('I run third');
defer(() => {
log('I run fourth');
}, 200);
}, 200);
}, 200);
// Log syncronously
log('I run first');
// Start the event loop turning (blocks until all events are complete)
const turns = loop.run();
log(`Loop exited after ${turns} turns`);
// This will log after event loop has finished running
log('I run last');
If you run this with node.js you'll get the following output:
t+0.000ms: I run first
t+200.000ms: I run second
t+400.000ms: I run third
t+600.000ms: I run fourth
t+600.000ms: Loop exited after 6441157 turns
t+600.000ms: I run last
We just created an asynchronous timeout in pure JavaScript with a single thread. Now in reality you wouldn't do this in JavaScript, the event loop would be implemented in native code and hosted in the host environment. An example of such an event loop is libuv used by Node.js. Libuv can do things more efficiently than our toy JS example, it can put the current thread to sleep (technically it doesn't do this, it polls for IO but same concept) so it's not wasting CPU cycles.
Those asynchronous functions are handled by the browser. not the JavaScript engine. there are no threads in JavaScript.
I have a long running for-loop in my code and I'd like to delay to loop to handle other tasks in the event queue (like a button press). Does javascript or JQuery have anything that could help me? Basically I'm trying to do something similar to delaying loops like here (https://support.microsoft.com/en-us/kb/118468).
If your application really requires long-running JavaScript code, one of the best ways to deal with it is by using JavaScript web workers. JavaScript code normally runs on the foreground thread, but by creating a web worker you can effectively keep a long-running process on a background thread, and your UI thread will be free to respond to user input.
As an example, you create a new worker like this:
var myWorker = new Worker("worker.js");
You can then post messages to it from the js in the main page like this:
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
And respond to the messages in worker.js like this:
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
With the introduction of generators in ES6, you can write a helper method that uses yield to emulate DoEvents without much syntactic overhead:
doEventsOnYield(function*() {
... synchronous stuff ...
yield; // Pump the event loop. DoEvents() equivalent.
... synchronous stuff ...
});
Here's the helper method, which also exposes the completion/failure of the function as a Promise:
function doEventsOnYield(generator) {
return new Promise((resolve, reject) => {
let g = generator();
let advance = () => {
try {
let r = g.next();
if (r.done) resolve();
} catch (ex) {
reject(ex);
}
setTimeout(advance, 0);
};
advance();
});
}
Note that, at this time, you probably need to run this through an ES6-to-ES5 transpiler for it to run on common browsers.
You can use the setTimeout:
setTimeout(function() { }, 3600);
3600 it's the time in milliseconds:
http://www.w3schools.com/jsref/met_win_settimeout.asp
There is no exact equivalent to DoEvents. Something close is using setTimeout for each iteration:
(function next(i) {
// exit condition
if (i >= 10) {
return;
}
// body of the for loop goes here
// queue up the next iteration
setTimeout(function () {
// increment
next(i + 1);
}, 0);
})(0); // initial value of i
However, that’s rarely a good solution, and is almost never necessary in web applications. There might be an event you could use that you’re missing. What’s your real problem?
Here's a tested example of how to use Yield as a direct replacement for DoEvents.
(I've used Web Worker and it's great, but it's far removed from DoEvents and near-impossible to access global variables). This has been formatted for ease of understanding and attempts to show how the extras required (to make the function handle yield) could be treated as an insertion within the original function. "yield" has all sorts of other features, but used thus, it is a near direct replacement for DoEvents.
//'Replace DoEvents with Yield ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield )
var misc = 0; //'sample external var
function myfunction() { //'This is the beginning of your original function which is effectively replaced by a handler inserted as follows..
//'-----------------------------------Insert Handler..
var obj = myfuncGen.next(); //'start it
if (obj.done == false) {
setTimeout(myfunction, 150); //'adjust for the amount of time you wish to yield (depends how much screen drawing is required or etc)
}
}
var myfuncGen = myfuncSurrogate(); //'creates a "Generator" out of next.
function* myfuncSurrogate() { //'This the original function repackaged! Note asterisk.
//'-------------------------------------End Insert
var ms; //...your original function continues here....
for (var i = 1; i <= 9; i++) { //'sample 9x loop
ms = new Date().getTime();
while (new Date().getTime() < ms + 500); //'PAUSE (get time & wait 500ms) as an example of being busy
misc++; //'example manipulating an external var
outputdiv.innerHTML = "Output Div<br>demonstrating progress.. " + misc;
yield; //'replacement for your doevents, all internal stack state and variables effectively hibernate.
}
console.log("done");
}
myfunction(); //'and start by calling here. Note that you can use "return" to return a value except by call backs.
<div id='outputdiv' align='center'></div>
..If you are new to all this, be aware that without the insertion and the yield keyword, you would simply wait 5 seconds while nothing happened and then the progress {div} would read "9" (because all the other changes to {div} were invisible).
I want a function of my choosing to run when a DOM element is added to the page. This is in the context of a browser extension, so the webpage runs independently of me and I cannot modify its source. What are my options here?
I guess that, in theory, I could just use setInterval() to continually search for the element's presence and perform my action if the element is there, but I need a better approach.
Warning!
This answer is now outdated. DOM Level 4 introduced MutationObserver, providing an effective replacement for the deprecated mutation events. See this answer to another question for a better solution than the one presented here. Seriously. Don't poll the DOM every 100 milliseconds; it will waste CPU power and your users will hate you.
Since mutation events were deprecated in 2012, and you have no control over the inserted elements because they are added by someone else's code, your only option is to continuously check for them.
function checkDOMChange()
{
// check for any new element being inserted here,
// or a particular node being modified
// call the function again after 100 milliseconds
setTimeout( checkDOMChange, 100 );
}
Once this function is called, it will run every 100 milliseconds, which is 1/10 (one tenth) of a second. Unless you need real-time element observation, it should be enough.
The actual answer is "use mutation observers" (as outlined in this question: Determining if a HTML element has been added to the DOM dynamically), however support (specifically on IE) is limited (http://caniuse.com/mutationobserver).
So the actual ACTUAL answer is "Use mutation observers.... eventually. But go with Jose Faeti's answer for now" :)
Between the deprecation of mutation events and the emergence of MutationObserver, an efficent way to be notified when a specific element was added to the DOM was to exploit CSS3 animation events.
To quote the blog post:
Setup a CSS keyframe sequence that targets (via your choice of CSS selector) whatever DOM elements you want to receive a DOM node insertion event for. I used a relatively benign and little used css property, clip I used outline-color in an attempt to avoid messing with intended page styles – the code once targeted the clip property, but it is no longer animatable in IE as of version 11. That said, any property that can be animated will work, choose whichever one you like.
Next I added a document-wide animationstart listener that I use as a delegate to process the node insertions. The animation event has a property called animationName on it that tells you which keyframe sequence kicked off the animation. Just make sure the animationName property is the same as the keyframe sequence name you added for node insertions and you’re good to go.
ETA 24 Apr 17
I wanted to simplify this a bit with some async/await magic, as it makes it a lot more succinct:
Using the same promisified-observable:
const startObservable = (domNode) => {
var targetNode = domNode;
var observerConfig = {
attributes: true,
childList: true,
characterData: true
};
return new Promise((resolve) => {
var observer = new MutationObserver(function (mutations) {
// For the sake of...observation...let's output the mutation to console to see how this all works
mutations.forEach(function (mutation) {
console.log(mutation.type);
});
resolve(mutations)
});
observer.observe(targetNode, observerConfig);
})
}
Your calling function can be as simple as:
const waitForMutation = async () => {
const button = document.querySelector('.some-button')
if (button !== null) button.click()
try {
const results = await startObservable(someDomNode)
return results
} catch (err) {
console.error(err)
}
}
If you wanted to add a timeout, you could use a simple Promise.race pattern as demonstrated here:
const waitForMutation = async (timeout = 5000 /*in ms*/) => {
const button = document.querySelector('.some-button')
if (button !== null) button.click()
try {
const results = await Promise.race([
startObservable(someDomNode),
// this will throw after the timeout, skipping
// the return & going to the catch block
new Promise((resolve, reject) => setTimeout(
reject,
timeout,
new Error('timed out waiting for mutation')
)
])
return results
} catch (err) {
console.error(err)
}
}
Original
You can do this without libraries, but you'd have to use some ES6 stuff, so be cognizant of compatibility issues (i.e., if your audience is mostly Amish, luddite or, worse, IE8 users)
First, we'll use the MutationObserver API to construct an observer object. We'll wrap this object in a promise, and resolve() when the callback is fired (h/t davidwalshblog)david walsh blog article on mutations:
const startObservable = (domNode) => {
var targetNode = domNode;
var observerConfig = {
attributes: true,
childList: true,
characterData: true
};
return new Promise((resolve) => {
var observer = new MutationObserver(function (mutations) {
// For the sake of...observation...let's output the mutation to console to see how this all works
mutations.forEach(function (mutation) {
console.log(mutation.type);
});
resolve(mutations)
});
observer.observe(targetNode, observerConfig);
})
}
Then, we'll create a generator function. If you haven't used these yet, then you're missing out--but a brief synopsis is: it runs like a sync function, and when it finds a yield <Promise> expression, it waits in a non-blocking fashion for the promise to be fulfilled (Generators do more than this, but this is what we're interested in here).
// we'll declare our DOM node here, too
let targ = document.querySelector('#domNodeToWatch')
function* getMutation() {
console.log("Starting")
var mutations = yield startObservable(targ)
console.log("done")
}
A tricky part about generators is they don't 'return' like a normal function. So, we'll use a helper function to be able to use the generator like a regular function. (again, h/t to dwb)
function runGenerator(g) {
var it = g(), ret;
// asynchronously iterate over generator
(function iterate(val){
ret = it.next( val );
if (!ret.done) {
// poor man's "is it a promise?" test
if ("then" in ret.value) {
// wait on the promise
ret.value.then( iterate );
}
// immediate value: just send right back in
else {
// avoid synchronous recursion
setTimeout( function(){
iterate( ret.value );
}, 0 );
}
}
})();
}
Then, at any point before the expected DOM mutation might happen, simply run runGenerator(getMutation).
Now you can integrate DOM mutations into a synchronous-style control flow. How bout that.
(see revisited answer at the bottom)
You can use livequery plugin for jQuery. You can provide a selector expression such as:
$("input[type=button].removeItemButton").livequery(function () {
$("#statusBar").text('You may now remove items.');
});
Every time a button of a removeItemButton class is added a message appears in a status bar.
In terms of efficiency you might want avoid this, but in any case you could leverage the plugin instead of creating your own event handlers.
Revisited answer
The answer above was only meant to detect that an item has been added to the DOM through the plugin.
However, most likely, a jQuery.on() approach would be more appropriate, for example:
$("#myParentContainer").on('click', '.removeItemButton', function(){
alert($(this).text() + ' has been removed');
});
If you have dynamic content that should respond to clicks for example, it's best to bind events to a parent container using jQuery.on.
Check out this plugin that does exacly that - jquery.initialize
It works exacly like .each function, the difference is it takes selector you've entered and watch for new items added in future matching this selector and initialize them
Initialize looks like this
$(".some-element").initialize( function(){
$(this).css("color", "blue");
});
But now if new element matching .some-element selector will appear on page, it will be instanty initialized.
The way new item is added is not important, you dont need to care about any callbacks etc.
So if you'd add new element like:
$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!
it will be instantly initialized.
Plugin is based on MutationObserver
A pure javascript solution (without jQuery):
const SEARCH_DELAY = 100; // in ms
// it may run indefinitely. TODO: make it cancellable, using Promise's `reject`
function waitForElementToBeAdded(cssSelector) {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (element = document.querySelector(cssSelector)) {
clearInterval(interval);
resolve(element);
}
}, SEARCH_DELAY);
});
}
console.log(await waitForElementToBeAdded('#main'));
With jQuery you can do -
function nodeInserted(elementQuerySelector){
if ($(elementQuerySelector).length===0){
setTimeout(function(){
nodeInserted(elementQuerySelector);
},100);
}else{
$(document).trigger("nodeInserted",[elementQuerySelector]);
}
}
The function search recursively for the node until it finds it then trigger an event against the document
Then you can use this to implement it
nodeInserted("main");
$(document).on("nodeInserted",function(e,q){
if (q === "main"){
$("main").css("padding-left",0);
}
});
How could something equivalent to lock in C# be implemented in JavaScript?
So, to explain what I'm thinking a simple use case is:
User clicks button B. B raises an onclick event. If B is in event-state the event waits for B to be in ready-state before propagating. If B is in ready-state, B is locked and is set to event-state, then the event propagates. When the event's propagation is complete, B is set to ready-state.
I could see how something close to this could be done, simply by adding and removing the class ready-state from the button. However, the problem is that a user can click a button twice in a row faster than the variable can be set, so this attempt at a lock will fail in some circumstances.
Does anyone know how to implement a lock that will not fail in JavaScript?
Lock is a questionable idea in JS which is intended to be threadless and not needing concurrency protection. You're looking to combine calls on deferred execution. The pattern I follow for this is the use of callbacks. Something like this:
var functionLock = false;
var functionCallbacks = [];
var lockingFunction = function (callback) {
if (functionLock) {
functionCallbacks.push(callback);
} else {
$.longRunning(function(response) {
while(functionCallbacks.length){
var thisCallback = functionCallbacks.pop();
thisCallback(response);
}
});
}
}
You can also implement this using DOM event listeners or a pubsub solution.
JavaScript is, with a very few exceptions (XMLHttpRequest onreadystatechange handlers in some versions of Firefox) event-loop concurrent. So you needn't worry about locking in this case.
JavaScript has a concurrency model based on an "event loop". This model is quite different than the model in other languages like C or Java.
...
A JavaScript runtime contains a message queue, which is a list of messages to be processed. To each message is associated a function. When the stack is empty, a message is taken out of the queue and processed. The processing consists of calling the associated function (and thus creating an initial stack frame) The message processing ends when the stack becomes empty again.
...
Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it can be stopped at any point to run some other code in another thread.
A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages.
For more links on event-loop concurrency, see E
I've had success mutex-promise.
I agree with other answers that you might not need locking in your case. But it's not true that one never needs locking in Javascript. You need mutual exclusivity when accessing external resources that do not handle concurrency.
Locks are a concept required in a multi-threaded system. Even with worker threads, messages are sent by value between workers so that locking is unnecessary.
I suspect you need to just set a semaphore (flagging system) between your buttons.
Here's a simple lock mechanism, implemented via closure
const createLock = () => {
let lockStatus = false
const release = () => {
lockStatus = false
}
const acuire = () => {
if (lockStatus == true)
return false
lockStatus = true
return true
}
return {
lockStatus: lockStatus,
acuire: acuire,
release: release,
}
}
lock = createLock() // create a lock
lock.acuire() // acuired a lock
if (lock.acuire()){
console.log("Was able to acuire");
} else {
console.log("Was not to acuire"); // This will execute
}
lock.release() // now the lock is released
if(lock.acuire()){
console.log("Was able to acuire"); // This will execute
} else {
console.log("Was not to acuire");
}
lock.release() // Hey don't forget to release
If it helps anyone in 2022+, all major browsers now support Web Locks API although experimental.
To quote the example in MDN:
await do_something_without_lock();
// Request the lock.
await navigator.locks.request('my_resource', async (lock) => {
// The lock has been acquired.
await do_something_with_lock();
await do_something_else_with_lock();
// Now the lock will be released.
});
// The lock has been released.
await do_something_else_without_lock();
Lock is automatically released when the callback returns
Locks are scoped to origins (https://example.com != https://example.org:8080), and work across tabs/workers.
Lock requests are queued (first come-first served); (unlike in some other languages where the lock is passed to some thread at random)
navigator.locks.query() can be used to see what has the lock, and who are in the queue to acquire the lock
There is a mode="shared" to implement a readers-writers lock if you need it
Why don't you disable the button and enable it after you finish the event?
<input type="button" id="xx" onclick="checkEnableSubmit('true');yourFunction();">
<script type="text/javascript">
function checkEnableSubmit(status) {
document.getElementById("xx").disabled = status;
}
function yourFunction(){
//add your functionality
checkEnableSubmit('false');
}
</script>
Happy coding !!!
Some addition to JoshRiver's answer according to my case;
var functionCallbacks = [];
var functionLock = false;
var getData = function (url, callback) {
if (functionLock) {
functionCallbacks.push(callback);
} else {
functionLock = true;
functionCallbacks.push(callback);
$.getJSON(url, function (data) {
while (functionCallbacks.length) {
var thisCallback = functionCallbacks.pop();
thisCallback(data);
}
functionLock = false;
});
}
};
// Usage
getData("api/orders",function(data){
barChart(data);
});
getData("api/orders",function(data){
lineChart(data);
});
There will be just one api call and these two function will consume same result.
Locks still have uses in JS. In my experience I only needed to use locks to prevent spam clicking on elements making AJAX calls.
If you have a loader set up for AJAX calls then this isn't required (as well as disabling the button after clicking).
But either way here is what I used for locking:
var LOCK_INDEX = [];
function LockCallback(key, action, manual) {
if (LOCK_INDEX[key])
return;
LOCK_INDEX[key] = true;
action(function () { delete LOCK_INDEX[key] });
if (!manual)
delete LOCK_INDEX[key];
}
Usage:
Manual unlock (usually for XHR)
LockCallback('someKey',(delCallback) => {
//do stuff
delCallback(); //Unlock method
}, true)
Auto unlock
LockCallback('someKey',() => {
//do stuff
})