I have multiple jQuery click event handlers, each that run asynchronous code (i.e. AJAX call) when clicked.
I have some code like this:
$(selector1).click((e) => {
asyncCode1();
)};
$(selector2).click((e) => {
asyncCode2();
)};
$(selector3).click((e) => {
asyncCode3();
)};
$(selector1).click(); // runs asyncCode1()
$(selector2).click(); // runs asyncCode2()
$(selector3).click(); // runs asyncCode3()
I want $(selector2).click() to run only after $(selector1).click() has completed execution, since asyncCode1() generates elements in the DOM that will eventually be selected in $(selector2).
Should I be returning promises on each of the click handlers? I'm not sure about best practices and how to go about doing this.
Yes, you can do it with Promise. Like this:
let asyncCode1Promise;
$(selector1).click((e) => {
asyncCode1Promise = asyncCode1(); // no await here!
)};
$(selector2).click(async (e) => {
await asyncCode1Promise;
asyncCode2();
)};
async asyncCode1() {
...
}
...
In that case, clicking selector2 will wait for completing code1 (if it's not complete yet). And if it was complete before clicking selector2, then asyncCode2 will run without any delay.
The good thing about using promises is that you can click selector2 multiple times, and it will work as expected (all clicks which are done before completion asyncCode1 will wait for its completion, and all clicks after asyncCode1 finished will call asyncCode2 immediately).
Related
I'm working with React, And I have a problem synchronizing clicks successively (onClick events):
My button triggers a function that calls two functions resetViews and chargeViews (chargeViews takes time), so when I click two times rapidely:
resetView is executed fine (it sets viewsRef.current to null)
then chargeViews is called but as it is taking time it won't update viewsRef.current right after.
so resetView is called but viewsRef.current is still null (because chargeViews doesn't update it yet) so it will do nothing,
then I get two delayed executions of chargeViews
so after two clicks I got 1 execution of resetView and two delayed executions of chargeViews
What I want is after clicking, block everything (even if the user clicks we do nothing) and execute resetView then chargeViews and then unblock to receive another click and do the same work.
<MyButton onClick={() => {
resetViews();
chargeViews();
}}>
Click me
</MyButton>
The function that takes time
const chargeViews = () => {
if(!viewsRef.current){
...
reader.setUrl(url).then(()=>{
reader.loadData().then(()=>{
...
viewsRef.current = reader.getData();
})})
}}
The function that getting ignored if I click so fast, (It works fine if I click and I wait a little bit then I click again) but if I click and click again fast It is ignored.
const resetViews = () => {
if (viewsRef.current){
...
viewsRef.current = null;
}}
I'm not totally sure to grasp the whole issue... this would be a comment if it didn't require such a long text.
Anyway as far as you need to disable the button once it's clicked you should deal with it at the beginning of its onclick handler:
$(this.event.target).prop('disabled', true);
and reset it at the end:
$(this.event.target).prop('disabled', false);
In general what you did was correct in terms of calling a number of functions to be executed in chain inside the handler. But those 2 functions seem to have a promise invocation.. in that case those won't be executed in chain waiting for the first one to finish.
So you should pass the second invocation as a callback to the first, to have the chance to call it ONLY once the first one finished its job.
I hope someone will just go straight to the point suggesting how making an async function "await" so that whatever it does, when invocated it will be waited for to complete before the next statement is evaluated. Usually it's just a matter of adding await before its signature but there are some caveats.
First, you need to convert your Promise-utilizing functions into async functions and then await them when invoking them.
This will make it easier to control the order of execution:
const chargeViews = async () => {
if(!viewsRef.current){
...
await reader.setUrl(url);
await reader.loadData();
...
viewsRef.current = reader.getData();
}
}
Then, you need an isExecuting ref that will be true when other invokations are executing and false when none are currently executing:
const isExecuting = useRef(false);
const handleClick = async () => {
if (!isExecuting.current) {
// block other clicks from performing actions in parallel
isExecuting.current = true;
try {
resetViews();
await chargeViews();
} finally {
// unblock other clicks
isExecuting.current = false;
}
}
};
Lastly, use the newly-created handleClick function in your JSX:
<MyButton onClick={handleClick}>
Click me
</MyButton>
I have an event listener in Node JS, as shown below.
client.on('collect', async reaction => {
await external.run(reaction, ...);
});
The function I called external.run returns a promise, and takes around 5 seconds to complete. If this event is triggered again while the previous trigger is still in execution (i.e before the 5 seconds it takes), it messes with my program.
Is there a way to wait for the previous execution to finish before running the new one?
Thanks.
Yes, what you want is called a Lock in other languages ... JS doesn't provide that mechanism natively, but its easy to write one yourself:
const createLock = () => {
let queue = Promise.resolve();
return task => queue = queue.then(() => task());
};
const externalLock = createLock();
client.on('collect', reaction => externalLock(async () => {
await external.run(reaction, ...);
}));
For sure this is only a contrived example, you might want to handle errors properly ... or you just use one of the libraries out there that do this
Currently, the code attaching the onClick events looks like this:
$("#LieblingsButton").click(function(){
reactivateFavSeatCheck()
})
$("#LieblingsButton").click(function(){
checkForWeekReservationByFavSeatButton()
})
$("#LieblingsButton").click(function(){
fetchDataFromDatabase()
})
$("#LieblingsButton").click(function(){
executeReservation()
})
fetchDataFromDatabase() does some async work, but this is already taken care of by async/await and promises.
executeReservation()shall ONLY start if fetchDataFromDatabase() has finished its execution.
Currently, everything is working. But I fear that this might only be the case because the cirumcstances allow for it. What if fetchDataFromDatabase() takes a few ms "too long"?
I already learned that when you add multiple event handlers to an element via jquery (how about with native JS?), they fire in the order which you have determined in your code. But I dont know if this "rule" also encompasses that Event2 will only fire if Event1 has already finished execution?
And besides, does the following code the same as the code above (from a functional perspective)?
$("#LieblingsButton").click(function(){
reactivateFavSeatCheck()
checkForWeekReservationByFavSeatButton()
fetchDataFromDatabase()
executeReservation()
})
First thing's first: JS execution is single-threaded in nature. No matter how asynchronous your code appears to be, only one part of it is running at any given time. You can read up more about this here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
Secondly, event listeners are triggered in a loop, in order of when they were attached. You can think of it sort of like this:
handlers.forEach((handler) => {
try { handler(event); } catch (e) { /* put warning in console */ }
});
But I fear that this might only be the case because the cirumcstances allow for it. What if fetchDataFromDatabase() takes a few ms "too long"?
With the following test, you can observe how a while loop in the first event listener stops the second one from firing, hence confirming your suspicion.
Note: I did not embed it as a snippet because snippets overriding of console somehow broke this example. Just paste it in your browser console.
// $(e).click(fn) roughly equals e.addEventListener('click', fn);
window.addEventListener('test', () => {
console.log('first handler', new Date());
const time = Date.now();
while (Date.now() - time < 2000) {}
});
window.addEventListener('test', () => {
console.log('second handler', new Date());
});
window.dispatchEvent(new Event('test'));
However...
If you are doing work asynchronously, things get much better.
window.addEventListener('test', () => {
setTimeout(() => {
console.log('first handler');
}, 1000);
});
window.addEventListener('test', () => {
console.log('second handler');
});
window.dispatchEvent(new Event('test'));
With this example you can see that although the first event handler schedules a timer, this does not block the next event listeners from running. The same is true if you were to say, make an XHR request.
So finally, armed with this information, we can say that it is actually better to use a single event listener, like in your second snippet.
I have a browser application where I want to change some text in the UI to say that the page is loading, then run a long process, and once the process is complete to say that the page is finished loading.
Using the code written below I can get this to work when I call ProperlyUpdatesUIAsync, where the text is changed while the long process is running, and then once the long process is complete, it changes again to indicate that it is done.
However, when I use the DoesNotUpdateUIUntilEndAsync method, the UI does not get updated until after the long process is finished, never showing the "loading" message.
Am I misunderstanding how async/await works with JavaScript? Why does it work in the one case but not in the other?
async function ProperlyUpdatesUIAsync(numberOfImagesToLoad) {
$("#PageStatusLabel").text("Loading..");
await pauseExecutionAsync(2000);
$("#PageStatusLabel").text("Loaded");
}
// this method doesn't do anything other than wait for the specified
// time before allowing execution to continue
async function pauseExecutionAsync(timeToWaitMilliseconds) {
return new Promise(resolve => {
window.setTimeout(() => {
resolve(null);
}, timeToWaitMilliseconds);
});
}
async function DoesNotUpdateUIUntilEndAsync(numberOfImagesToLoad) {
$("#PageStatusLabel").text("Loading..");
await runLongProcessAsync();
$("#PageStatusLabel").text("Loaded");
}
async function runLongProcessAsync() {
// there is a for loop in here that takes a really long time
}
Edit:
I experimented with a few things and this new refactor is giving me the desired result, but I do not like it. I wrapped the long running loop in a setTimeout with a timeout setting of 10. With a value of 10, the UI is updated before running the loop. However, a value of 0 or even 1 does not allow the UI to update, and it continues to behave as if the timeout was not declared at all. 10 seems so arbitrary. Can I really rely on that working in every scenario? Shouldn't async/await defer execution until the UI is updated without my having to wrap everything in a timeout?
async function runLongProcessThatDoesNotBlockUIAsync() {
return new Promise(resolve => {
window.setTimeout(() => {
// there is a for loop in here that takes a really long time
resolve(null);
}, 10);
});
}
EDITED
The code in runLongProcessAsync() never yeilds/surrenders the thread for updates to take place.
try: -
<!DOCTYPE html>
<html>
<script type="text/javascript">
var keep;
async function DoesNotUpdateUIUntilEndAsync(numberOfImagesToLoad) {
document.getElementById("PageStatusLabel").innerHTML="Loading..";
p = new Promise((resolve) => {keep = resolve})
setTimeout(theRest,0); //let the Loading message appear
return p;
}
async function theRest(){
await runLongProcessAsync(); // Your await here is useless!
document.getElementById("PageStatusLabel").innerHTML="Loaded";
keep();
}
async function runLongProcessAsync() {
// there is a for loop in here that takes a really long time
for (var x=1; x<1000000000;x++){b=x^2}
}
</script>
<body onload="DoesNotUpdateUIUntilEndAsync(5)">
<p>Test</p>
<p id="PageStatusLabel"></p>
</body>
</html>
I'm not sure what you are attempting but my guess is you want Web Worker to give you another thread. Either that or you don't understand that "await" just gets rid of the need for callbacks. If your code is purely synchronous simply labelling "async" does nothing.
I have several ASP.NET UpdatePanels, each with an AsyncPostBackTrigger tied to the same button's serverside click event. Since only one UpdatePanel can be doing its thing at a time, I use .get_isInAsyncPostBack() of the PageRequestManager to prevent a user from being able to access another part of the page until the async postback is complete.
Another part of this page needs to dynamically update multiple update panels consecutively. Since the update panels use async triggers, calling __doPostBack("<%=ButtonName.ClientID %>", 'PanelId'); fires asynchonously. Because of this, it will quickly move along to the next iteration of the loop and try to update the next panel. However, the second iteration fails because there is already another update panel doing an async postback.
Ideally, there would be a way to wait until .get_isInAsyncPostBack() returns false without blocking other client activity.
Research has lead me to a lot people with my problem, almost all of whom are advised to use setTimeOut(). I do not thing this will work for me. I don't want to wait for a specified amount of time before executing a function. I simply want my Javascript to wait while another script is running, preferably wait until a specific condition is true.
I understand that many will probably want to suggest that I rethink my model. It's actually not my model, but one that was handed to our development team that is currently a total mess under the hood. Due to time contraints, rewriting the model is not an option. The only option is to make this work. I think that if I had a way to make the client code wait without blocking, my problem would be solved.
There is no such functionality such as wait or sleep in javascript, since it would stop browser from responding.
In your case I would go with something similar to following:
function wait(){
if (!condition){
setTimeout(wait,100);
} else {
// CODE GOES IN HERE
}
}
It's easy to make a mistake when calling setTimeout that will cause the JavaScript call stack to fill up. If your function has parameters, you need to pass those in at the end of the setTimeout parameter list like this:
function wait(param1, param2){
if (!condition){
setTimeout(wait, 100, param1, param2);
} else {
// CODE GOES IN HERE
}
}
If you pass parameters or even include empty () after the name of the function, it will be executed immediately and fill up the stack.
// This is the wrong way to do it!
function wait(param1, param2){
if (!condition){
setTimeout(wait(param1, param2), 100); // you'll get max call stack error if you do this!
} else {
// CODE GOES IN HERE
}
}
I needed to slow down a process and came up with a helpful little method.
const wait = (seconds) =>
new Promise(resolve =>
setTimeout(() => resolve(true), seconds * 1000)
);
And you can use it like this.
const doWork = async() => {
// After 3 seconds do something...
await wait(3);
console.log('work done');
}
This function calls condFunc which should return true when condition is met. When that happens readyFunc is called. checkInterval sets checking rate in milliseconds
var wait = function(condFunc, readyFunc, checkInterval) {
var checkFunc = function() {
if(condFunc()) {
readyFunc();
}
else
{
setTimeout(checkFunc, checkInterval);
}
};
checkFunc();
};
Usage:
wait(
function() { return new Date().getSeconds() == 10; },
function() { console.log("Done"); },
100
);
prints "Done" when current time is 10 seconds after minute