Understanding NodeJs await with setTimeout() method - javascript

I'm fairly new to NodeJs and understanding the concept of async/await. Please correct me if I'm wrong - await keyword blocks the code until it gets a resolved value. For example:
const sampleFunction = async () => {
const result = await someAsynFunctionReturningPromise();
console.log('Log is printed here!');
}
In the above code, the compiler stops the code at 'const result = await someAsynFunctionReturningPromise();' until 'someAsynFunctionReturningPromise()' gets resolved, right?
With the above assumption, I tried the below setTimeout() code:
const sampleFunction = async () => {
const result = await setTimeout(()=>{
console.log('This is printed after 2 seconds');
},2000);
console.log('Log is printed here!');
}
However, the above code doesnt await till setTimeout() is resolved, it skips to the next line, printing 'Log is printed here!'.
Can anyone please help me understand if my understanding is wrong?

In order for await to work with setTimeout, setTimeout needs to return a promise. Please see this: How to make a promise from setTimeout

This is where the event loop of node JS working comes in. In the first place your
someAsynFunctionReturningPromise()
is returning a promise where in the second code setTimeout() does not return you promise. Also when nodejs eventloop gets such a function like setTimeout() it will put it in callstack and then to taskqueue where it will wait for 2000ms and goes for the next line of execution, that is why it is printing
console.log('Log is printed here!');
when 2000ms are over the setTimeout will be kept from taskqueue to callstack and it will execute
console.log('This is printed after 2 seconds');

You wrote your own answer. You wrote
await keyword blocks the code until it gets a resolved value
In your code you never resolved anything so of course it won't work
A setTimeout that resolves is
function wait(ms = 0) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, ms);
};
}
or the terse version
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
Example
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const sampleFunction = async () => {
for(;;) {
await wait(2000);
console.log('time since page loaded:', performance.now() / 1000 | 0);
}
};
sampleFunction();

Related

Order of execution of back to back JavaScript Promises

In the following code:
new Promise((resolve, reject) => {
asyncFunc1(resolve,reject);
})
.then((result) => {
// do then1 stuff
})
new Promise((resolve, reject) => {
asyncFunc2(resolve,reject);
})
.then((result) => {
// do then2 stuff
})
Will asyncFunc1()'s .then() complete before asyncFunc2() is executed?
Or will both execute (nearly) simultaneously, and their then()s execute just whenever they happen to return?
If the second one does not wait for the first one, is there a way to make it do so other than moving asyncFunc2() into asyncFunc1()'s .then()?
Both promises will execute (nearly) simultaneously, because that is exactly one of the strengths of Javascript: Due to its callback-based design, it can kick off and wait for many asynchronous functions (in the form of promises) in parallel, without the need for the developer to care about complex handling of threads or anything like that.
If you want to have asyncFunc2 wait for the completion of asyncFunc1, it needs to be placed inside the then-block of asyncFunc1.
You can easily see and proof that by simulating two functions which take a defined time (using setTimeout), for example:
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved at ' + new Date().toISOString());
}, 2000);
});
}
function resolveAfter3Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved at ' + new Date().toISOString());
}, 3000);
});
}
function execPromises() {
console.log('calling at ' + new Date().toISOString());
resolveAfter2Seconds().then(result => console.log(result));
resolveAfter3Seconds().then(result => console.log(result));
}
execPromises();
You will see in the console output that the first one will finish 2 sec after starting the script, the other one 3 sec after starting the script (and not after 2 + 3 = 5 sec).
If you want to make asyncFunc2 wait for asyncFunc1 without the need for then, you can use the async/await keywords.
In my example, the function execPromises would then look like this (asyncFunc2 executed after completion of asyncFunc1!):
async function execPromises() {
console.log('calling at ' + new Date().toISOString());
const resultA = await resolveAfter2Seconds();
console.log(resultA);
const resultB = await resolveAfter3Seconds();
console.log(resultB);
}

JavaScript (React): How to extend async function's await to a given time

I would like to have a async function that sends a request to a server and awaits the response. It will then wait upto 1000ms to ensure that an animation has played out.
i was wondering if I could combine these 2 tasks somehow so they add up to having waited 1000ms in total dynamically.
My current code:
const loadingHandler = async (func, target) => {
setIsLoading(true);
await new Promise(r => setTimeout(r, 1000));
await func(target);
setIsLoading(false);
};
Some examples to further explain what exactly I mean: the function calls func() and gets a response after (lets say) 200ms. Now the timeout is only supposed to be 800ms. If the func() returns after 20ms the timeout is supposed to be 980ms. If func() takes longer than 1000ms it should continue immedietly after getting the response and not wait additionally.
So is something like this 'stretcher await function' possible?
Sure, just remember how long it's been so far, then delay that much longer:
const loadingHandler = async (func, target) => {
setIsLoading(true);
const started = Date.now();
await func(target);
const elapsed = Date.now() - started;
const delayFurther = 1000 - elapsed;
if (delayFurther > 0) {
await new Promise(r => setTimeout(r, delayFurther));
}
setIsLoading(false);
};
That said, holding up the user because an animation hasn't finished might not be the best UX. Perhaps you could make the animation finish more quickly when func is already done. (Humans tend to notice delays > about 80-100ms, a bit less when we're younger.)
An alternative to Mr Crowder's perfectly valid solution: Rather than wait for the promise to finish and then checking if we need to start a new one, start two promises at the same time and wait for them both. Has the same effect, but makes the code shorter:
const loadingHandler = async (func, target) => {
setIsLoading(true);
await Promise.all([
func(target),
new Promise(r => setTimeout(r, 1000)),
]);
setIsLoading(false);
}
In your code you starts first promise (interval), waits for it to finish, then starts second (func) and wait to finish. You should start both promises and wait for it together.
const loadingHandler = async (func, target) => {
setIsLoading(true);
const p1 = new Promise(r => setTimeout(r, 1000));
const p2 = func(target);
await Promise.all([p1,p2]);
setIsLoading(false);
};

Nested multilayered async/await doesn't seem to wait

I have a piece of code simplified version of which looks like this:
let dataStorage1; //declare global vars for easier access later on
let dataStorage2;
let stopLight = true; //this variable is used to 'mark' an iteration as successful (= true) or
//failed (= false) and in need of a retry before continuing to the next
//iteration
let delay = 2000; //the standard time for a delay between api calls
async function tryFetch() {
try {
dataStorage1 = await api.fetch('data_type_1'); //fetch needed data trough api, which
//fills the global variable with an
//object
dataStorage2 = await api.fetch('data_type_2'); //do the same
stopLight = true; //change the value of stopLight to true, thus marking this iteration
//as successful
} catch (err) {
console.log(err);
stopLight = false;
}
}
async function fetchData() {
stopLight = true; //change the stopLight to default before execution
await tryFetch(); //fetch data and assign it to variables
//this section is needed for retrial of fetching after a 2s delay if the first attempt was
//unsuccessful, which is repeated until it's either successful or critical error occurred
while (stopLight == false) {
setTimeout(async () => await tryFetch(), delay);
}
}
(async function main() {
await fetchData(); //finally call the function
setTimeout(main, delay); //repeat the main function after 2s
})();
As you can see, self-executing, pseudo-recursive main() calls for await fetchData(), then fetchData() calls for await tryFetch() and finally tryFetch() calls for await api.fetch('~'), as it's defined in the api.
However, once I started the script and paused it after a couple of iterations, I noticed that both dataStorage1 and dataStorage2 remain undefined. If I go through the code step by step in debugger, what happens is that the execution starts at the beginning of fetchData(), moves to the await tryFetch(); line, skips it, and then goes onto the next iteration.
For the reference, if I call dataStorage1/2 = await api.fetch(`~`); in the body of main() directly without any nesting, it works perfectly (unless error occurs, since they are not handled properly).
So, my question is what have I missed?
Indeed, if in an async function you call setTimeout you cannot expect it to perform an await on anything that relates to the callback passed to setTimeout. The call to setTimeout returns immediately, and your while loop is effectively a synchronous loop. It is a so called "busy loop" -- blocking your GUI as it potentially will loop for thousands of times.
As a rule of thumb, use setTimeout only once: to define a delay function, and then never again.
Also avoid using a global variable like stopLight: this is bad practice. Let the async function return a promise that resolves when this is supposed to be true, and rejects when not.
// Utility function: the only place to use setTimeout
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function tryFetch() {
try {
let dataStorage1 = await api.fetch('data_type_1');
let dataStorage2 = await api.fetch('data_type_2');
return { dataStorage1, dataStorage2 }; // use the resolution value to pass results
} catch (err) {
console.log(err);
// retry
throw err; // cascade the error!
}
}
async function fetchData() {
while (true) {
try {
return await tryFetch(); // fetch data and return it
} catch (err) {} // repeat loop
}
}
(async function main() {
let intervalTime = 2000; //the standard time for a delay between api calls
while (true) { // for ever
let { dataStorage1, dataStorage2 } = await fetchData();
// ... any other logic that uses dataStorage1, dataStorage2
// should continue here...
await delay(intervalTime); //repeat the main function after 2s
}
})();
I think the problem is in this line: setTimeout(async () => await tryFetch(), delay);. The await statement inside the callback makes the promise returned by that callback wait, not the whole function. So async () => await tryFetch() is a function that returns a promise, but nothing waits for that promise to complete.
Try replacing that code with something line
await new Promise((resolve) => setTimeout(resolve, delay));
await tryFetch();

Promise resolved too fast

I am using Promises and in the meanwhile i have a loading animation.
the problme is that my promise is resolved fast and the loader is quickly disappear.
So i want to launch a promise and if the promise is resolved before 3 sec wait the remaining time.
Example
export const operation = () => {
const a = new Date();
const myPromise = doAction().then(() => {
const b = new Date();
if((b - a) < 3000)
operationIsDone();
else
setTimeout(() => {operationIsDone();}, b - a)
});
}
Is there any npm or a better way doing it?
Thanks in advance.
It is much easier to use a second promise that just runs the minimum waiting time. Then use Promise.all to wait for both to finish.
That way, your script will always wait at least the default delay but also longer if yourOwnPromise takes longer than that.
const wait = delay => new Promise(resolve => setTimeout(resolve, delay));
const doAction = () => wait(500); // TODO replace this with your own function
const yourOwnPromise = doAction();
yourOwnPromise.then(() => {
console.log('yourOwnPromise resolved now');
});
Promise.all([yourOwnPromise, wait(3000)]).then(() => {
console.log('both resolved now');
});
See Promise.all for details.

If I await 2 functions can I guarantee that the return object will have both values

I have the following code
module.exports = async function (req, res) {
const station1 = await getStation('one')
const station2 = await getStation('two')
return { stations: [station1, station2] }
}
Can I be guaranteed that when the final return value is sent it will definitely have both station1 and station2 data in them, or do I need to wrap the function call in a Promise.all()
As you have it, it is guaranteed that the return statement will only be executed when the two getStation() promises have resolved.
However, the second call to getStation will only happen when the first promise has resolved, making them run in serial. As there is no dependency between them, you could gain performance, if you would run them in parallel.
Although this can be achieved with Promise.all, you can achieve the same by first retrieving the two promises and only then performing the await on them:
module.exports = async function (req, res) {
const promise1 = getStation('one');
const promise2 = getStation('two');
return { stations: [await promise1, await promise2] }
}
Now both calls will be performed at the "same" time, and it will be just the return statement that will be pending for both promises to resolve. This is also illustrated in MDN's "simple example".
The await keyword actually makes you "wait" on the line of code, while running an async action.
That means that you don't proceed to the next line of code until the async action is resolved. This is good if your code has a dependency with the result.
Example:
const res1 = await doSomething();
if(res1.isValid)
{
console.log('do something with res1 result');
}
The following code example will await a promise that gets resolved after three seconds. Check the date prints to the console to understand what await does:
async function f1() {
console.log(new Date());
// Resolve after three seconds
var p = new Promise(resolve => {
setTimeout(() => resolve({}),3000);
});
await p;
console.log(new Date());
}
f1();
ES6Console
BTW, In your case, since you don't use the result of station1 it's better using Promise.all to work parallel.
Check this example (it will run for 3 seconds instead of 4 seconds the way you coded above):
async function f1() {
console.log(new Date());
// Resolve after three seconds
var p1 = new Promise(resolve => {
setTimeout(() => resolve({a:1}),3000);
});
// Resolve after one second
var p2 = new Promise(resolve => {
setTimeout(() => resolve({a:2}),1000);
});
// Run them parallel - Max(three seconds, one second) -> three seconds.
var res = await Promise.all([p1,p2]);
console.log(new Date());
console.log('result:' + res);
}
f1();
ES6Console.
If either of await getStation('one') or await getStation('two') fails an exception will be thrown from the async function. So you should always get the resolved value from both promises.
You can rewrite your function as follows to use Promise.all
module.exports = async function (req, res) {
try{
const [station1, station2] = await Promise.all([
getStation('one'),
getStation('two')
]);
return { stations: [station1, station2] };
} catch (e) {
throw e;
}
}

Categories

Resources