JS Foreach Alternative (timeout) for Queue - javascript

I'm working on the "Approve All" button. The process here is when I click "Approve All," each individual "Approve" button will be triggered as "click" all at once, and then it will send POST requests to the controller. However, when I clicked Approve All button, there was a race condition causing the controller returns Error 500: Internal server error. I have tried using JS setTimeout() with value 1500*iter, but when the iterator gets higher, for example at i = 100, then it would take 1500*100 => 150000ms (150s). I hope that explains the problem clearly. Is there a way to prevent such a case?
Here is my code, I'm using JQuery:
let inspection = $this.parents("li").find("ul button.approve"); // this will get all 'approve' button to be clicked at once
inspection.each((i,e)=>{
(function () {
setTimeout(function () {
$(e).data("note",r);
$(e).click();
}, 1500 * i); // this acts like a queue, but when i > 100, it takes even longer to send POST requests.
})(this,i,e,r);
});
// then, each iteration will send a POST request to the controller.
$("#data-inspection ul button.approve").on("click", function() {
// send POST requests
});
Any help would be much appreciated. Thank you.

That 500 error may also be the server crashing from being unable to process all the requests simultaneously.
What I'd recommend is using an event-driven approach instead of setTimeout. Your 1500ms is basically a guess - you don't know whether clicks will happen too quickly, or if you'll leave users waiting unnecessarily.
I'll demonstrate without jQuery how to do it, and leave the jQuery implementation up to you:
// use a .js- class to target buttons your buttons directly,
// simplifying your selectors, and making them DOM agnostic
const buttonEls = document.querySelectorAll('.js-my-button');
const buttonsContainer = document.querySelector('.js-buttons-container');
const startRequestsEvent = new CustomEvent('customrequestsuccess');
// convert the DOMCollection to an array when passing it in
const handleRequestSuccess = dispatchNextClickFactory([...buttonEls]);
buttonsContainer.addEventListener('click', handleButtonClick);
buttonsContainer.addEventListener(
'customrequestsuccess',
handleRequestSuccess
);
// start the requests by dispatching the event buttonsContainer
// is listening for
buttonsContainer.dispatchEvent(startRequestsEvent);
// This function is a closure:
// - it accepts an argument
// - it returns a new function (the actual event listener)
// - the returned function has access to the variables defined
// in its outer scope
// Note that we don't care what elements are passed in - all we
// know is that we have a list of elements
function dispatchNextClickFactory(elements) {
let pendingElements = [...elements];
function dispatchNextClick() {
// get the first element that hasn't been clicked
const element = pendingElements.find(Boolean);
if (element) {
const clickEvent = new MouseEvent('click', {bubbles: true});
// dispatch a click on the element
element.dispatchEvent(clickEvent);
// remove the element from the pending elements
pendingElements = pendingElements.filter((_, i) => i > 0);
}
}
return dispatchNextClick;
}
// use event delegation to mitigate adding n number of listeners to
// n number of buttons - attach to a common parent
function handleButtonClick(event => {
const {target} = event
if (target.classList.contains('js-my-button')) {
fetch(myUrl)
.then(() => {
// dispatch event to DOM indicating request is complete when the
// request succeeds
const completeEvent = new CustomEvent('customrequestsuccess');
target.dispatchEvent(completeEvent);
})
}
})
There are a number of improvements that can be made here, but the main ideas here are that:
one should avoid magic numbers - we don't know how slowly or quickly requests are going to be processed
requests are asynchronous - we can determine explicitly when they succeed or fail
DOM events are powerful
when a DOM event is handled, we do something with the event
when some event happens that we want other things to know about, we can dispatch custom events. We can attach as many handlers to as many elements as we want for each event we dispatch - it's just an event, and any element may do anything with that event. e.g. we could make every element in the DOM flash if we wanted to by attaching a listener to every element for a specific event
Note: this code is untested

Related

how to prevent a event listener function to be triggered several times at the same time

my question is easy I'm using discord js and have this event listener in my code.
this listener function is triggered when a user sends a particular message in a channel. But if 2 users send this particular message at the same time the listener function is triggered twice at the same time. How can I prevent this ?
If you are happy to ignore the second message coming through you could have a look at wrapping your function with debouncing, which would cause it to only trigger once if called in short succession.
Lodash has a package for this that can be imported individually
import { myFunc } from 'somewhere';
import { debounce } from 'somewhereElse';
const DEBOUNCE_DELAY_MS = 500;
const myDebouncedFunc = debounce(myFunc, DEBOUNCE_DELAY_MS);
// Call myDebouncedFunc twice immediately after each other, the
// debouncing will result in the function only getting called max once
// every 500ms;
myDebouncedFunc();
myDebouncedFunc();
Otherwise, if you need to have both messages processed, just not at the same time, then you would need something like a queue for processing these events. Then you could process these messages in an interval for example.
// Some lexically available scope
const myQueue = [];
// Event handler
const myHandler = (msg) => {
myQueue.push(msg);
}
// Interval processing
setInterval(() => {
if (myQueue.length > 0) {
const msgToProcess = myQueue.shift();
processMessage(msgToProcess);
}
}, 500)
There are multiple solutions to this problem depending on your needs.
Unregister the event listener as soon as the even comes. In most libraries, you can easily achieve by registering your event using once() method, instead of "on()"
Use debounce() to aggregate all the events triggered within a certain amount of time into one.
The same or similar methods exist in most of JS libraries similar to lodash.

How does stream.Readable.prototype.on handle events in node.js source code?

I was confused when reading code of stream.Readable in Node.js.
here is source code:
https://github.com/nodejs/node/blob/master/lib/_stream_readable.js#L778-L799
Readable.prototype.on = function(ev, fn) {
const res = Stream.prototype.on.call(this, ev, fn);
if (ev === 'data') {
// Start flowing on next tick if stream isn't explicitly paused
if (this._readableState.flowing !== false)
this.resume();
} else if (ev === 'readable') {
const state = this._readableState;
if (!state.endEmitted && !state.readableListening) {
state.readableListening = state.needReadable = true;
state.emittedReadable = false;
if (!state.reading) {
process.nextTick(nReadingNextTick, this);
} else if (state.length) {
emitReadable(this);
}
}
}
return res;
};
Obviously, the if statements only handle data and Readable event, but according to the API document, the on method of stream.readable also accept other events such as close , end , error.
So my question is :
According to the source code, how did stream.Readable handle other events except data and readable?
What you are seeing here is an override for the .on() method so that the Readable class can watch what event listeners are being attached and can do something special when someone installs a listener for the data event or the readable event.
The first line of this function:
const res = Stream.prototype.on.call(this, ev, fn);
is where the Readable passes the callback and event name arguments to its parent so that the normal implementation will be run. A Stream implements the EventEmitter interface so calling the super method with Stream.prototype.on.call(this, ev, fn) will give .on() it's expected default behavior.
Then after calling the parent, it checks to see if the event that someone is listening to is the data event or readable event and then implements a little extra functionality when one of those event listeners is attached.
For the data event, it resumes the stream so that it will start flowing if it was paused and if it was set to flowing mode. This is probably because when a Readable is being initially created and configured, if it starts flowing the stream before the data event listener is attached, then data on the stream could be missed. So, it doesn't start flowing until someone is around to listen to data events.
Note, there are potentially lots of over events that occur on the stream and those are all handled by the call to the base class in the first line. What you are seeing here is just some special behavior that the Readable class wants to implement when two specific event listeners are first added. This code does not affect when those events are sent or how they are listened to. It just triggers a little behavior in the Readable state when a listener for one of these events is first attached.

document.open removes all window listeners

I'm writing a Chrome extension. It's used for recording users' behavior on browsing web pages. It does that by adding event listeners to customers' web pages, using Chrome content script.
Code in content script looks like:
var recordingEvents = ['click', 'input', 'change'];
recordingEvents.forEach(function (e) {
window.addEventListener(e, handler, true);
});
Example of custom page:
<script>
function reload() {
var ifrw = document.getElementById("iframeResult").contentWindow;
ifrw.document.open();
ifrw.document.write("<div>abc</div>");
ifrw.document.close();
}
</script>
<body>
<input type="submit" onclick="reload();" value="Reload" />
<iframe id="iframeResult"></iframe>
</body>
It uses document.open, document.write to rewrite content of iframe.
Here is the question. My event listeners are attached to window object. And document.open removes all its event listeners. Like picture below shows.
Is there a way to avoid document.open removing event listeners? Or to observe document.open, so I can manually re-add listeners after it?
I've found this issue trying to solve exactly the same problem.
Here is a spec https://html.spec.whatwg.org/multipage/webappapis.html#dom-document-open that says that on document.open current document is destroyed and replaced with a fresh one. I had a hope that some event's like "load" are still preserved, no luck.
Here is my detection code:
const testEventName = 'TestEvent';
let tm;
function onTestEvent() {
clearTimeout(tm);
}
function listenToTestEvent() {
document.addEventListener(testEventName, onTestEvent);
}
listenToTestEvent();
function onLostEvents() {
console.log('events are lost');
listenToTestEvent();
// DO THING HERE
}
function checkIfEventsAreLost() {
document.dispatchEvent(new CustomEvent(testEventName));
tm = setTimeout(onLostEvents);
}
new MutationObserver(checkIfEventsAreLost).observe(document, { childList: true });
When document is recreated its childList is changed(new documentElementnode), this is the best trigger I've thought of to detect document replacement.
Note that even listeners fire before setTimeout(..., 0)
This is a detailed explanation of why #Viller's answer works. I'm making this a new answer as it didn't fit into a comment
The TestEvent event is a special event that monitors when the events that were previously setup in a document are removed.
In particular, this accounts for the case of document.open, which removes all listeners not only from the document but also from the window.
The general idea is to setup a listener for a custom event called TestEvent, which clears a timeout. Such timeout is setup only when the document mutates and is triggered by a mutation observer.
Since the timeout schedules the operation to happen at least during the next tick of the event loop, such timeout can be cleared before that, avoiding the execution of its callback all together. And, since the TestEvent event handler clears that timeout, the fact that the timeout is cleared implies that the listener is still attached. On the other hand, if the timeout is not cleared before the next tick, the would signify the events were removed and a new "setup" is needed.
According to MDN:
The Document.open() method [...] come(s) with some side effects. For example:
All event listeners currently registered on the document, nodes inside
the document, or the document's window are removed.
Below I provide a module (onGlobalListenerRemoval) where one can easily register some callback functions to get notified whenever listeners get cleared. This uses the same working principle as the code in Viller's answer.
Usage principle:
onGlobalListenerRemoval.addListener(() => {
alert("All event listeners got removed!")
});
Module code:
const onGlobalListenerRemoval = (() => {
const callbacks = new Set();
const eventName = "listenerStillAttached";
window.addEventListener(eventName, _handleListenerStillAttached);
new MutationObserver((entries) => {
const documentReplaced = entries.some(entry =>
Array.from(entry.addedNodes).includes(document.documentElement)
);
if (documentReplaced) {
const timeoutId = setTimeout(_handleListenerDetached);
window.dispatchEvent(new CustomEvent(eventName, {detail: timeoutId}));
}
}).observe(document, { childList: true });
function _handleListenerDetached() {
// reattach event listener
window.addEventListener(eventName, _handleListenerStillAttached);
// run registered callbacks
callbacks.forEach((callback) => callback());
}
function _handleListenerStillAttached(event) {
clearTimeout(event.detail);
}
return {
addListener: c => void callbacks.add(c),
hasListener: c => callbacks.has(c),
removeListener: c => callbacks.delete(c)
}
})();

Handling JavaScript events not handled by other elements

I am writing a script where I would like to handle mouse events only if they have not been handled by any other element before.
I can attach an event listener to the document object, but it will receive all events regardless of whether they have been already handled.
I have no control over the elements in the HTML page so I cannot manually stopPropagation() when the event is handled.
Any ideas?
From this article here.
It seems its not yet possible to do this.
Which event handlers are registered?
One problem of the current implementation of W3C’s event registration
model is that you can’t find out if any event handlers are already
registered to an element. In the traditional model you could do:
alert(element.onclick)
and you see the function that’s registered to
it, or undefined if nothing is registered. Only in its very recent DOM
Level 3 Events W3C adds an
eventListenerList
to store a list of event
handlers that are currently registered on an element. This
functionality is not yet supported by any browser, it’s too new.
However, the problem has been addressed.
Fortunately
removeEventListener()
doesn’t give any errors if the event
listener you want to remove has not been added to the element, so when
in doubt you can always use removeEventListener().
So, you can accomplish something like you want under a certain set of circumstances. Specifically, if
You can load your own custom JavaScript before the original
You know which elements you are listening for others to throw events on
Effectively, what you do is replace the original addEventListener method on the target element with a custom one that intercepts the call, does some special processing, and then lets it continue per normal. This 'special processing' is a new function that wraps the original callback, and marks the event arguments with some state to let you know someone else handeled the event already. Here is a proof of concept (with a jsFiddle)
Target HTML:
<div id='asdf'>asdf</div>​
JavaScript:
var target = document.getElementById('asdf');
var original = target.addEventListener;
var updated = function(){
// Grab the original callback, so we can use it in our wrapper
var originalFunc = arguments[1];
// Create new function for the callback, that lets us set a state letting us know it has been handled by someone
// Finish the callback by executing the original callback
var newFunc = function(e){
console.log('haha, intercepted you');
e.intercepted = true;
originalFunc.call(this, e);
};
// Set the new function in place in the original arguments 'array'
arguments[1] = newFunc;
// Perform the standard addEventListener logic with our new wrapper function
original.apply(this, arguments);
};
// Set the addEventListener on our target to our modified version
target.addEventListener = updated;
// Standard event handling
target.addEventListener('click', function(e){
console.log('original click');
console.log('intercepted?', e.intercepted);
})

How to let JavaScript wait until certain event happens?

I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.

Categories