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.
Related
I'm attempting to get a better grasp on async functions and promises in JS. To do this, I wrote a sample program that has the goal of calling a function that does busy work (purposefully not using async setTimeout as I want to mimic a long-running process) but returns immediately. However, I can't seem to figure out why this isn't working.
test();
async function intense(){
var start = new Date().getTime();
for (var i = 0; i < 1e6; i++) {
if ((new Date().getTime() - start) > 2000){
break;
}
}
console.log("Done with async work");
}
async function test(){
console.log("Print 1");
intense(); // does some busy work for a few seconds
console.log("Print 2"); // want this to print immediately after print 1
}
When I run it, I get:
Print 1
Done with async work
Print 2
And I would like it to be:
Print 1
Print 2
Done with async work
I thought that it would print the latter sequence because I declared the function intense() to be async, so it would return a promise immediately and continue work asynchronously.
I even tried to refactor the intense function to be a promise that resolves immediately, but to no avail.
async function intense(){
return new Promise((resolve)=> {
resolve();
var start = new Date().getTime();
for (var i = 0; i < 1e6; i++) {
if ((new Date().getTime() - start) > 2000){
break;
}
}
console.log("Done with async work");
}, null)
}
What am I missing?
There a are a couple of reasons for what you're seeing:
An async function is synchronous up until its first await or return, so the entire function runs before returning in your case.
Busy-waiting isn't asynchronous.
test needs to use await if it's going to wait for intense to complete before continuing.
Moving something into a promise doesn't take it off the thread. The only way to do that in most JavaScript environments (including browsers) is to use a Worker thread (MDN, Node.js docs — Node.js has had Worker since ~v10.5, and while it's still marked "experimental" the main parts of the API should be fairly stable as they're drawn from the web worker standard API).
It's important to remember that promises don't make anything asynchronous¹, they provide a means of observing the result of something that's already asynchronous.
Here's an example using setTimeout for the asynchronous part that help you understand these better:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function intense(value) {
console.log("intense(" + value + ") - This is synchronous");
await delay(100);
console.log("intense(" + value + ") - This is asynchronous, because it's after `await`");
}
async function test(){
console.log("Print 1");
intense(1); // <== WITHOUT await
console.log("Print 2");
await intense(2); // <== With await
console.log("Print 3");
}
test();
.as-console-wrapper {
max-height: 100% !important;
}
¹ There's one small caveat to that: The handler you pass to then, catch, or finally will always be called asynchronously, even if the promise you're calling them on is already settled. That's literally the only thing that promises actually make asynchronous.
so it would return a promise immediately and continue work asynchronously.
No it would not. The callback passed to the Promise constructor is called immeadiately. What is async is the process of calling resolve or reject later on, and how .then chains get called back somewhen.
However it isnt async in the sense that the code runs on another thread or gets deferred, that won't happen as JS itself is executed in a single thread*.
console.log(1);
const promise = new Promise((resolve, reject) => {
console.log(2); // gets executed immeadiately
});
promise.then(() => console.log(4)); // < Promise resolve asynchronously
console.log(3);
*If you plan to do really "intense" work, it might be benefitial to do that in another thread (see WebWorker in browsers and child_process.spawn for NodeJS).
This is a misunderstanding that is pretty easy to make with javascript. Your function intense() blocks the thread. Putting something in an async function does not change the fact that you only have one thread in javascript. As soon as the interpreted starts running that for loop it's going to use the one thread to run it until it's over. Nothing else will happen until then.
Async functions don't return immediately, they run the body of the code until the hit an await and return a promise. In your example, the entire function will run before it returns.
You can't use this kind of long running process without blocking. That's why node goes out of its way to offload things like i/o access and timers to another thread.
See here for more details: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
If you want to run some long-running code like this asynchronously you will need to spawn a child process: https://nodejs.org/api/child_process.html
I don't know if it is possible, but I really want to force a normal function to stop and wait for a async function. Here is a simple example:
function draw_a_cat(){
draw_cat_body();
// get_head_information is asynchronous, and I want to STOP execution and wait for it to get info we need in order to draw the cat.
wait for get_head_information();
draw_head();
display_cat_name();
return true;
}
cat_drawn_yet = draw_a_cat();
console.log("drawn")!
I am not actually working on drawing cats, but I need to know how to do this. I wan't draw_a_cat to wait for get_head_information, meaning draw_cat will not return until it has gotten the head information and drawn the head. The head won't be drawn until the head information has been obtained, so that we can draw the head correctly.
And, the console.log won't happen until the cat is fully drawn and cat_drawn_yet == "true". draw_a_cat is not an async function, meaning it cannot just await get_head_information().
I want the program to stop and wait for the async get_head_information before continuing normally. I know it sounds stupid to freeze everything else, waiting for get_head_information, but I need to do this in a more complicated project where it just makes sense.
You could work with the result from the get_head_information inside the draw_a_cat function without making it async or returning a Promise. However, you would not be able to get a meaningful return value from the draw_a_cat function.
Here is an example using the then method of Promises. The logging of the result "drawn" is moved inside the draw_a_cat function in the callback passed to the then method.
Note that if there are more statements after the call to draw_a_cat, they will be executed before the code in the then callback.
const draw_cat_body = () => { };
const draw_head = () => { };
const display_cat_name = () => { };
const get_head_information = () => Promise.resolve('round and fluffy');
function draw_a_cat() {
draw_cat_body();
// can wait here
get_head_information()
.then(info => {
// you can work with what get_head_information returned
draw_head(info); // assuming drawing the head is synchronous
display_cat_name();
console.log("drawn");
})
.catch(err => {
console.log(err);
});
}
// can NOT wait here
draw_a_cat();
I know JS is single threaded. But I have a function which takes time for the calculation. I would like it to work paralleled, so this function would not freeze next statement. Calculation inside of function will take approximately 1-2 seconds.
I used to create it using promise, but it still freeze the next statement.
console.log("start");
new Promise((res, rej) => {
/* calculations */
}).then((res) => console.log(res));
console.log("end");
Then I used setTimeout function with time interval 0. LoL
console.log("start");
setTimeout(() => {
/* calculations */
console.log(res);
}, 0);
console.log("end");
Outputs:
start
end
"calculation result"
Both cases shows similar result, but using promise prevented to show console.log("end") before calculation finishes. Using setTimeout works as I wanted, and shows console.log("end") before calculation, so it was not freeze till calculation done.
I hope it was clear enough. Now for me using setTimeout is the best solution, but I would be happy to hear your ideas or any other method calculating concurrently without setTimeout.
The code you write under new Promise(() => {..code here..}) is not asynchronous. The is a very common misconception that everything under the Promise block would run asynchronously.
Instead, this JS API just let's get us a hook of some deferred task to be done once the promise is resolved. MDN
Promises are a comparatively new feature of the JavaScript language that allow you to defer further actions until after a previous action
has completed, or respond to its failure. This is useful for setting
up a sequence of async operations to work correctly.
new Promise(() => {
// whatever I write here is synchromous
// like console.log, function call, setTimeout()/fetch()/async web apis
// if there are some async tasks like fetch, setTimeout.
// they can be async by themselves but their invocation is still sync
//
})
setTimeout is not the correct option either. Code under setTimeout would run when the event stack is empty and once it enters, it would block the main thread again.
The right approach to this would be to use Web Workers.
I am new to node js and trying to understand how to make async calls.
I am trying to wrap a function into a promise in order to make it async. For simplicity, the function is just a loop that takes time :
var p = new Promise(resolve => {
for (let i=0;i<999999999;i++){}
resolve('foo')
});
p.then(function (value) { console.log(value);});
console.log('bar');
I am expecting to see :
bar // printed immediately
foo // printed after the loop finishes
Instead, they are both printed after the loop completion.
How to make the loop block run asynchronously?
Ps. Apologies for the indentation/formatting, I had to write this from a phone.
Thanks
You seem to assume that "asynchronous code" involves concurrency, in the sense that code will run at the same time in another thread. That is not the case. Unless you start another thread by purpose, JS itself will run in a single thread. Therefore no matter what you do with promises: Either the loop runs first and then the logs run or the logs run first and then the loop runs.
You could also achieve concurrent execution through multitasking: If you stop the loop inbetween, other code can run in the meantime:
(async function() {
while(true) {
console.log("loop");
await Promise.resolve();
}
})();
console.log("in the meantime");
But there's nothing asynchronous about your promise. Creating a promise starts the execution of the function, and JS always runs to completion.
Typically, a promise is used to initiate something asynchronous, like an API call, or even a plain setTimeout, that runs outside JS's thread. Your code, however, will iterate through the empty loop bajillion times, and only after that any following lines will be run.
Replace the empty line with a timeout, and it will become async:
var p = new Promise(resolve => {
setTimeout(() => resolve("foo"), 2000);
});
p.then(function(value) {
console.log(value);
});
console.log('bar');
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