Why does this JavaScript async/await code not behave as expected? - javascript

I've tried reading guides and tutorials to async/await, but I can't seem to find this addressed anywhere.
Here is the code in question:
var func1 = new Promise((resolve, reject) => {
console.log("Func1");
setTimeout(() => {
resolve(10);
}, 100);
});
var func2 = new Promise((resolve, reject) => {
console.log("Func2");
setTimeout(() => {
resolve(20);
}, 5000);
})
let run = async() => {
let var1 = await func1;
let var2 = await func2;
console.log(var1);
console.log(var2);
}
run();
We see "Func1" and "Func2" get printed immediately, one after the other. 5 seconds later, the timeout specified in func2, we get "10" and "20" printed. So far so good.
But if I change the last bit of code to this:
let run = async() => {
let var1 = await func1;
console.log(var1);
let var2 = await func2;
console.log(var2);
}
Then I see "Func1" immediately printed, but "Func2" as well, even though the console.log(var1) comes before it. After 100ms comes "10", then after 5 seconds comes "20".
From MDN:
The await expression causes async function execution to pause until a Promise is fulfilled or rejected, and to resume execution of the async function after fulfillment.
But it doesn't seem like this is what's happening. If it was, wouldn't we see "Func1", then "10", THEN func2 gets executed, thus printing "Func2" and 5 seconds later we get "20"? func1 should be executed, and once it is resolved (in 100 ms), console.log(var1) should fire. What am I missing here?

The reason for this is that you already ran your promise executors before. Promise executors are evaluated straight away when passed to the Promise constructor, so you'll get the following:
var func1 = new Promise((resolve, reject) => ...); // Logs "Func1"
var func2 = new Promise((resolve, reject) => ...); // Logs "Func2"
let run = async() => {
let var1 = await func1;
console.log(var1); // Logs 10
let var2 = await func2;
console.log(var2); // Logs 20
}
run();

The main problem here is func1 and func2 are not functions; They are promises. Like Jonas W. says, promises will call their callback (resolve, reject) => immediately, and awaiting them just causes them to wait until they are resolved.
You can see your desired result here:
var func1 = () => {
return new Promise((resolve, reject) => {
console.log("Func1");
setTimeout(() => {
resolve(10);
}, 100);
});
}
var func2 = () => {
return new Promise((resolve, reject) => {
console.log("Func2");
setTimeout(() => {
resolve(20);
}, 5000);
});
}
let run = async() => {
let var1 = await func1();
let var2 = await func2();
console.log(var1);
console.log(var2);
}
run();

await basically means enshure that the following value is available before continuing. The promise starts resolving when you create it, it does not matter when it is awaited.

The run function is in synchronous order e.g. the code takes it step by step:
call func1() and wait until there is a reply.
call func2() and wait until there is a reply.
log var1.
log var2.
So the last 2 steps will only run when the call to func2() has completed the promise e.g. the data after the timeout. So it will wait the full 5 seconds until logging both var1 and var2.
The other way is fairly different
1. call func1() and wait until there is a reply.
2. log var1.
3. call func2() and wait until there is a reply.
4. log var2.
Step 2 will wait until step one has returned data e.g. after the 100ms and then will move on to doing the same for step 3 and 4.

Related

Can I prevent an `AsyncGenerator` from yielding after its `return()` method has been invoked?

AsyncGenerator.prototype.return() - JavaScript | MDN states:
The return() method of an async generator acts as if a return statement is inserted in the generator's body at the current suspended position, which finishes the generator and allows the generator to perform any cleanup tasks when combined with a try...finally block.
Why then does the following code print 0–3 rather than only 0–2?
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const values = (async function* delayedIntegers() {
let n = 0;
while (true) {
yield n++;
await delay(100);
}
})();
await Promise.all([
(async () => {
for await (const value of values) console.log(value);
})(),
(async () => {
await delay(250);
values.return();
})(),
]);
I tried adding log statements to better understand where the "current suspended position" is and from what I can tell when I call the return() method the AsyncGenerator instance isn't suspended (the body execution isn't at a yield statement) and instead of returning once reaching the yield statement the next value is yielded and then suspended at which point the "return" finally happens.
Is there any way to detect that the return() method has been invoked and not yield afterwards?
I can implement the AsyncIterator interface myself but then I lose the yield syntax supported by async generators:
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const values = (() => {
let n = 0;
let done = false;
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
if (done) return { done, value: undefined };
if (n !== 0) {
await delay(100);
if (done) return { done, value: undefined };
}
return { done, value: n++ };
},
async return() {
done = true;
return { done, value: undefined };
},
};
})();
await Promise.all([
(async () => {
for await (const value of values) console.log(value);
})(),
(async () => {
await delay(250);
values.return();
})(),
]);
Why does the code print 0–3 rather than only 0–2? From what I can tell, when I call the return() method, the AsyncGenerator instance isn't suspended (the body execution isn't at a yield statement) and instead of returning once reaching the yield statement the next value is yielded and then suspended at which point the "return" finally happens.
Yes, precisely this is what happens. The generator is already running because the for await … of loop did call its .next() method, and so the generator will complete that before considering the .return() call.
All the methods that you invoke on an async generator are queued. (In a sync generator, you'd get a "TypeError: Generator is already running" instead). One can demonstrate this by immediately calling next multiple times:
const values = (async function*() {
let i=0; while (true) {
await new Promise(r => { setTimeout(r, 1000); });
yield i++;
}
})();
values.next().then(console.log, console.error);
values.next().then(console.log, console.error);
values.next().then(console.log, console.error);
values.return('done').then(console.log, console.error);
values.next().then(console.log, console.error);
Is there any way to detect that the return() method has been invoked and not yield afterwards?
No, not from within the generator. And really you probably still should yield the value if you already expended the effort to produce it.
It sounds like what you want to do is to ignore the produced value when you want the generator to stop. You should do that in your for await … of loop - and you can also use it to stop the generator by using a break statement:
const delay = (ms) => new Promise((resolve) => {
setTimeout(resolve, ms);
});
async function* delayedIntegers() {
let n = 0;
while (true) {
yield n++;
await delay(1000);
}
}
(async function main() {
const start = Date.now();
const values = delayedIntegers();
for await (const value of values) {
if (Date.now() - start > 2500) {
console.log('done:', value);
break;
}
console.log(value);
}
})();
But if you really want to abort the generator from the outside, you need an out-of-band channel to signal the cancellation. You can use an AbortSignal for this:
const delay = (ms, signal) => new Promise((resolve, reject) => {
function done() {
resolve();
signal?.removeEventListener("abort", stop);
}
function stop() {
reject(this.reason);
clearTimeout(handle);
}
signal?.throwIfAborted();
const handle = setTimeout(done, ms);
signal?.addEventListener("abort", stop, {once: true});
});
async function* delayedIntegers(signal) {
let n = 0;
while (true) {
yield n++;
await delay(1000, signal);
}
}
(async function main() {
try {
const values = delayedIntegers(AbortSignal.timeout(2500));
for await (const value of values) {
console.log(value);
}
} catch(e) {
if (e.name != "TimeoutError") throw e;
console.log("done");
}
})();
This will actually permit to stop the generator during the timeout, not after the full second has elapsed.
Is there a way to prevent this "extra yield" after invoking the return method? If not, are there libraries, patterns, etc. our there that avoid this while still implementing these AsyncIterator interface optional properties?
As #Bergi clearly explained, the extra yield cannot be avoided with the AsyncGenerator.return() method. This is a really interesting case, but I don't think you will find libraries that fix it. #Bergi proposed a clever solution using the AbortSignal, I have tried a different approach with only Promises:
(async function test() {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const wrapIntoStoppable = function (generator) {
const newGenerator = {
isGeneratorStopped: false,
resolveStopPromise: null,
async *[Symbol.asyncIterator]() {
let stoppedSymbol = Symbol('stoppedPromise')
let stoppingPromise
while (true) {
if (this.isGeneratorStopped)
return
stoppingPromise = new Promise((resolve, _) => this.resolveStopPromise = resolve)
.then(_ => stoppedSymbol)
nextValuePromise = generator.next()
const result = await Promise.race([nextValuePromise, stoppingPromise])
this.resolveStopPromise() // resolve the promise in case it is still pending
if (result === stoppedSymbol)
return
else
yield result.value
}
},
stop: function() {
this.resolveStopPromise()
this.isGeneratorStopped = true
}
}
const handler = {
get: function(target, prop, receiver) {
if (['next', 'return', 'throw'].includes(prop))
return generator[prop].bind(generator)
else
return newGenerator[prop].bind(newGenerator)
}
}
return new Proxy(newGenerator, handler)
}
const values = wrapIntoStoppable((async function* delayedIntegers() {
let n = 0;
while (true) {
yield n++;
await delay(100);
}
})());
await Promise.all([
(async () => {
for await (const value of values) {
console.log(Date.now())
console.log(value);
}
console.log(Date.now())
// console.log(await values.next())
// console.log(await values.return())
// console.log(await values.throw())
})(),
(async () => {
await delay(250);
values.stop()
})(),
]);
})();
The idea is that I wrap an async generator with an object that has an async iterator. All the elements yielded by the wrapping generator are yielded by the original generator, but now 2 promises are started:
nextValuePromise that will return the value to yield
stoppingPromise that will end the iteration if resolved before the previous one
In this way, if the stop() method (which resolves stoppingPromise) is called before the first promise is resolved, then Promise.race() will immediately return a dummy Symbol. When the result of the race is this symbol, the iterator returns. The stop() function also sets the isGeneratorStopped flag that makes sure the iterator will eventually return if the stop() method is called after the stoppingPromise() is manually resolved.
I have also used a Proxy to make sure that the wrapping object behaves as a true AsyncGenerator. Calls to next(), return() or throw() are simply forwarded to the wrapped generator.
Let's see the pros:
wrapIntoStoppable can become a util method that just wraps any async generator. This is certainly convenient because you don't have to use signals every time there is a pending Promise 1
Once the stop() method is called on the async generator, the for await...of loop immediately returns. Note: this doesn't mean that pending Promises are aborted
And now the cons:
Maybe too much code to maintain? Now the generator has a proxy that wraps another wrapper... I would like to simplify the design at least
After the generator is stopped, the nextValuePromise() could be resolved in the meantime, causing some potential side effects. This is the main reason why it is a pretty dangerous library function.
Actually, I think you could even merge #Bergi's and my solution and manage to abort a Promise when the stop() method is called. However, in this case, all the promises need to handle the abort signals.
await would suspend the async part of the function, but not the generator part, thus AsyncGenerator.return() can not act on the await suspension, but only yield suspension. And I think that's why AsyncGenerator.return() returns a promise, but Generator.return() does not.
Yes. Bergi is right. The for await loop invokes .next() right after the consumption and puts yield in charge before return. So what happens is;
# 0ms 0 gets yielded and .next() puts yield in charge to yield 1 once resolved.
#100ms 1 gets yielded and .next() puts yield in charge to yield 2 once resolved.
#200ms 2 gets yielded and .next() puts yield in charge to yield 3 once resolved.
#250ms a values.return() is enqueued but yield has already been queued to yield 3.
#300ms 3 gets yielded and generator finalizes along with the iterable values.
Now the thing is, if we find a way to resolve or reject the promise waiting for 3 prematurely #250ms then we are fine. Yet without using the abort abstraction you can still do this with naked promises and even without using an async generator. You just need to lift the resolve and reject functions out of the generator functions scope and invoke from there. I think it's best to reject prematurely and catch the rejection at the outer scope (silent or not).
Here is a way to accomplish this;
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
prior = {};
const values = (function* delayedIntegers() {
let n = 0,
p = new Promise((v,x) => Object.assign(prior,{v,x}));
prior.v(n++); // resolve p with 0 and increment n
while (true) {
yield p
p = new Promise((v,x) => Object.assign(prior,{v,x}));
delay(100).then(_ => prior.v(n++));
}
})();
(async () => {
try {
for await (const value of values) console.log(value);
}
catch(e){
console.log(e);
values.return();
}
})();
delay(250).then(_ => prior.x("Finalized..!"));
This is almost like your code but there is this prior object which holds the resolve and reject functions of a promise callback as v and x respectively.
Since prior object is accessible from within the outer context we can invoke it's x method (rejection) before the while loop in the generator resolves 3 and catch the rejection with the employed catch(e).

JavaScript How to execute firstPromise before secondPromise

This might seem a silly question, but I am a newbie in this topic. In the script below i have two promises. By now "secondPromise" executing first, and then executing "firstPromise". Cuz in the "secondPromise" i set less time. But how to execute "firstPromise" first, after finished that, start executing the "secondPromise"?
How to rewrite the script below
(async function()
{
//var final = new Array();
var final;
const firstPromise = new Promise(
function(resolve)
{
let result = 2 + 2;
resolve(result);
setTimeout(() => console.log("please show me first"), 2000);
});
const secondPromise = new Promise(
function(resolve)
{
let result2 = 0;
resolve(result2 + 1);
setTimeout(() => console.log("please show me second"), 1000);
});
var myP = Promise.all([firstPromise, secondPromise]).then((values) => {
return values[0]+values[1];
});
return myP;
})();
(async function()
{
//var final = new Array();
var final;
const firstPromise = new Promise(
function(resolve)
{
let result = 2 + 2;
resolve(result);
setTimeout(() => console.log("please show me first"), 2000);
});
const secondPromise = new Promise(
function(resolve)
{
let result2 = 0;
resolve(result2 + 1);
setTimeout(() => console.log("please show me second"), 1000);
});
var myP = Promise.all([firstPromise, secondPromise]).then((values) => {
return values[0]+values[1];
});
return myP;
})();
Quentin's answer is correct: the function you pass to new Promise happens immediately. However: because you already have this in an async function, you can await Promises within it. This pauses the async function until the Promise resolves, so unlike your function with Promise.all that waits for your explicit new Promise Promises in parallel, my function waits for those Promises serially.
Furthermore, if you want the new Promise constructions you wrote to wait for the action in setTimeout, you need to wait and call the resolve method within the callback that setTimeout calls, not outside them as you have it.
console.log("start");
(async function () {
const firstValue = await new Promise(
// ^^^^^
function (resolve) {
let result = 2 + 2;
setTimeout(() => {
console.log("please show me first");
resolve(result); // Promise resolves after 2000ms
}, 2000);
});
const secondValue = await new Promise(
// ^^^^^
function (resolve) {
let result2 = 0;
setTimeout(() => {
console.log("please show me second");
resolve(result2 + 1);
}, 1000);
});
// These are values, not promises, because you await them.
// However, the async function still returns a Promise, because
// "async" covers the asynchronicity of the Promises you await.
return firstValue + secondValue;
})().then(x => console.log(x)); // or .then(console.log)
You can't execute a promise.
You can execute a function.
If you pass a function to a Promise constructor, then it will be executed (by the Promise constructor) immediately.
There is no way to delay the execution of a function passed to a Promise constructor.
You could wrap your calls to new Promise in functions, and then only call the second function when the first promise is resolved.
(Note, however, that in your examples the calls to setTimeout happen after the resolve function is called).

Rearming a promise when used with setInterval and setTimeout [duplicate]

function first(){
console.log('first')
}
function second(){
console.log('second')
}
let interval = async ()=>{
await setInterval(first,2000)
await setInterval(second,2000)
}
interval();
Imagine that I have this code above.
When I run it, first() and second() will be called at the same time; how do I call second() after first)() returns some data, for example, if first() is done, only then call second()?
Because first() in my code will be working with a big amount of data and if this 2 functions will be calling at the same time, it will be hard for the server.
How do I call second() each time when first() will return some data?
As mentioned above setInterval does not play well with promises if you do not stop it. In case you clear the interval you can use it like:
async function waitUntil(condition) {
return await new Promise(resolve => {
const interval = setInterval(() => {
if (condition) {
resolve('foo');
clearInterval(interval);
};
}, 1000);
});
}
Later you can use it like
const bar = waitUntil(someConditionHere)
You have a few problems:
Promises may only ever resolve once, setInterval() is meant to call the callback multiple times, Promises do not support this case well.
Neither setInterval(), nor the more appropriate setTimeout() return Promises, therefore, awaiting on them is pointless in this context.
You're looking for a function that returns a Promise which resolves after some times (using setTimeout(), probably, not setInterval()).
Luckily, creating such a function is rather trivial:
async function delay(ms) {
// return await for better async stack trace support in case of errors.
return await new Promise(resolve => setTimeout(resolve, ms));
}
With this new delay function, you can implement your desired flow:
function first(){
console.log('first')
}
function second(){
console.log('second')
}
let run = async ()=>{
await delay(2000);
first();
await delay(2000)
second();
}
run();
setInterval doesn't play well with promises because it triggers a callback multiple times, while promise resolves once.
It seems that it's setTimeout that fits the case. It should be promisified in order to be used with async..await:
async () => {
await new Promise(resolve => setTimeout(() => resolve(first()), 2000));
await new Promise(resolve => setTimeout(() => resolve(second()), 2000));
}
await expression causes async to pause until a Promise is settled
so you can directly get the promise's result without await
for me, I want to initiate Http request every 1s
let intervalid
async function testFunction() {
intervalid = setInterval(() => {
// I use axios like: axios.get('/user?ID=12345').then
new Promise(function(resolve, reject){
resolve('something')
}).then(res => {
if (condition) {
// do something
} else {
clearInterval(intervalid)
}
})
}, 1000)
}
// you can use this function like
testFunction()
// or stop the setInterval in any place by
clearInterval(intervalid)
You could use an IFFE. This way you could escape the issue of myInterval not accepting Promise as a return type.
There are cases where you need setInterval, because you want to call some function unknown amount of times with some interval in between.
When I faced this problem this turned out to be the most straight-forward solution for me. I hope it help someone :)
For me the use case was that I wanted to send logs to CloudWatch but try not to face the Throttle exception for sending more than 5 logs per second. So I needed to keep my logs and send them as a batch in an interval of 1 second. The solution I'm posting here is what I ended up using.
async function myAsyncFunc(): Promise<string> {
return new Promise<string>((resolve) => {
resolve("hello world");
});
}
function myInterval(): void {
setInterval(() => {
void (async () => {
await myAsyncFunc();
})();
}, 5_000);
}
// then call like so
myInterval();
Looked through all the answers but still didn't find the correct one that would work exactly how the OP is asked. This is what I used for the same purpose:
async function waitInterval(callback, ms) {
return new Promise(resolve => {
let iteration = 0;
const interval = setInterval(async () => {
if (await callback(iteration, interval)) {
resolve();
clearInterval(interval);
}
iteration++;
}, ms);
});
}
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
(async () => {
console.log('start');
await waitInterval(first, 1000);
await waitInterval(second, 1000);
console.log('finish');
})()
In my example, I also put interval iteration count and the timer itself, just in case the caller would need to do something with it. However, it's not necessary
In my case, I needed to iterate through a list of images, pausing in between each, and then a longer pause at the end before re-looping through.
I accomplished this by combining several techniques from above, calling my function recursively and awaiting a timeout.
If at any point another trigger changes my animationPaused:boolean, my recursive function will exit.
const loopThroughImages = async() => {
for (let i=0; i<numberOfImages; i++){
if (animationPaused) {
return;
}
this.updateImage(i);
await timeout(700);
}
await timeout(1000);
loopThroughImages();
}
loopThroughImages();
Async/await do not make the promises synchronous.
To my knowledge, it's just a different syntax for return Promise and .then().
Here i rewrote the async function and left both versions, so you can see what it really does and compare.
It's in fact a cascade of Promises.
// by the way no need for async there. the callback does not return a promise, so no need for await.
function waitInterval(callback, ms) {
return new Promise(resolve => {
let iteration = 0;
const interval = setInterval(async () => {
if (callback(iteration, interval)) {
resolve();
clearInterval(interval);
}
iteration++;
}, ms);
});
}
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
// async function with async/await, this code ...
(async () => {
console.log('start');
await waitInterval(first, 1000);
await waitInterval(second, 1000);
console.log('finish');
})() //... returns a pending Promise and ...
console.log('i do not wait');
// ... is kinda identical to this code.
// still asynchronous but return Promise statements with then cascade.
(() => {
console.log('start again');
return waitInterval(first, 1000).then(() => {
return waitInterval(second, 1000).then(() => {
console.log('finish again');
});
});
})(); // returns a pending Promise...
console.log('i do not wait either');
You can see the two async functions both execute at the same time.
So using promises around intervals here is not very useful, as it's still just intervals, and promises changes nothing, and make things confusing...
As the code is calling callbacks repeatedly into an interval, this is, i think, a cleaner way:
function first(i) {
console.log(`first: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function second(i) {
console.log(`second: ${i}`);
// If the condition below is true the timer finishes
return i === 5;
}
function executeThroughTime(...callbacks){
console.log('start');
let callbackIndex = 0; // to track current callback.
let timerIndex = 0; // index given to callbacks
let interval = setInterval(() =>{
if (callbacks[callbackIndex](timerIndex++)){ // callback return true when it finishes.
timerIndex = 0; // resets for next callback
if (++callbackIndex>=callbacks.length){ // if no next callback finish.
clearInterval(interval);
console.log('finish');
}
}
},1000)
}
executeThroughTime(first,second);
console.log('and i still do not wait ;)');
Also, this solution execute a callback every secondes.
if the callbacks are async requests that takes more than one sec to resolve, and i can't afford for them to overlap, then, instead of doing iterative call with repetitive interval, i would get the request resolution to call the next request (through a timer if i don't want to harass the server).
Here the "recursive" task is called lTask, does pretty much the same as before, except that, as i do not have an interval anymore, i need a new timer each iteration.
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync1(i) {
console.log(`first pending: ${i}`);
return new Promise((resolve) =>{
setTimeout(() => resolve('got that first big data'), Math.floor(Math.random()*1000)+ 1000);//simulate request that last between 1 and 2 sec.
}).then((result) =>{
console.log(`first solved: ${i} ->`, result);
return i==2;
});
}
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync2(i) {
console.log(`second pending: ${i}`);
return new Promise((resolve) =>{
setTimeout(() => resolve('got that second big data'), Math.floor(Math.random()*1000) + 1000);//simulate request that last between 1 and 2 sec.
}).then((result) =>{ // promise is resolved
console.log(`second solved: ${i} ->`,result);
return i==4; // return a promise
});
}
function executeThroughTime(...asyncCallbacks){
console.log('start');
let callbackIndex = 0;
let timerIndex = 0;
let lPreviousTime = Date.now();
let lTask = () => { // timeout callback.
asyncCallbacks[callbackIndex](timerIndex++).then((result) => { // the setTimeout for the next task is set when the promise is solved.
console.log('result',result)
if (result) { // current callback is done.
timerIndex = 0;
if (++callbackIndex>=asyncCallbacks.length){//are all callbacks done ?
console.log('finish');
return;// its over
}
}
console.log('time elapsed since previous call',Date.now() - lPreviousTime);
lPreviousTime = Date.now();
//console.log('"wait" 1 sec (but not realy)');
setTimeout(lTask,1000);//redo task after 1 sec.
//console.log('i do not wait');
});
}
lTask();// no need to set a timer for first call.
}
executeThroughTime(simulateAsync1,simulateAsync2);
console.log('i do not wait');
Next step would be to empty a fifo with the interval, and fill it with web request promises...

Why should we store Promise objects in variables?

Let us suppose that we've got a function that resolves a promise like below:
function timeoutPromise(interval) {
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve("done");
}, interval);
});
};
and let us suppose that we call that function inside an asynchronous function in two different ways;
slow synchornous way:
async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}
Here we simply await all three timeoutPromise() calls directly. Each subsequent one is forced to wait until the last one finished, this will result in total run time of around 9 seconds.
and fast asynchronous way:
async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}
Here we store the three Promise objects in variables, which has the effect of setting off their associated processes all running simultaneously. This will result in total run time of around 3 seconds.
But the question is, why storing Promise objects in variables has the effect setting off their associated processs running simultaneously? what happens under the hood?
await foo();
await bar();
will only call bar (and thus create the second promise) after the promise returned by foo is resolved.
var x = foo();
var y = bar();
await x;
calls bar (and thus creating the second promise) before the promise returned by foo is resolved.
That's what makes the promises "concurrent". If you add console.log in various places you will see the difference in execution:
function timeoutPromise(name, interval) {
return new Promise((resolve, reject) => {
console.log(`Promise ${name} created.`);
setTimeout(function(){
console.log(`Promise ${name} resolved.`);
resolve("done");
}, interval);
});
};
async function timeTest1() {
console.log('test 1');
await timeoutPromise(1, 3000);
console.log('between promise 1 and 2');
await timeoutPromise(2, 3000);
}
async function timeTest2() {
console.log('test 2');
const timeoutPromise1 = timeoutPromise(1, 3000);
console.log('between promise 1 and 2');
const timeoutPromise2 = timeoutPromise(2, 3000);
await timeoutPromise1;
console.log('between promise 1 and 2 with await');
await timeoutPromise2;
}
timeTest1().then(timeTest2);

Why does my timeout wait until after the await completes but my log does not?

If I have something like this setup:
<-- language: lang-javascript -->
console.clear();
// noprotect
const fetchSomething = () => new Promise((resolve) => {
setTimeout(() => resolve('future value'), 500);
});
async function asyncFunction() {
const result = await fetchSomething();
console.log('waiting');
setTimeout(()=>console.log('waiting?'), 250);
return result + ' 2';
}
asyncFunction().then(result => console.log(result));
And my output looks like:
"waiting"
"future value 2"
"waiting?"
I would expect the waiting? to execute before the result completes, but for some reason it waits on the function. What makes one wait but the other execute?
It is a feature of Asynchronous programming.
You have to add await and wrap your setTimeout(()=>console.log('waiting?'), 250); with a function which returns Promise in order to make it look like it was evaluated continuously.
Something like:
await ((ms) =>{
console.log('waiting?');
return new Promise(resolve => setTimeout(resolve, ms));
})(250);
Or:
await (() => new Promise(resolve => setTimeout(()=>{
console.log('waiting?');
resolve();
}, 250)))();
Mind you, JS has a single threaded run-time engine, so it interrupts evaluation when original script reaches it's end.
And function in setTimeout(function, timeout) is evaluated by JS when it has a first chance and time is right.
So your function was interrupted twice and was resumed twice.
The call to log "waiting?" is started by a setTimeout after the await has finished, so after the 500ms in fetchSomething have already passed. It will only execute 250ms after fetchSomething has returned. That is enough time for asyncFunction to return.
If you want to see a different behaviour, start the timer for logging "waiting?" before calling await:
async function asyncFunction() {
setTimeout(()=>console.log('waiting?'), 250);
const result = await fetchSomething();
console.log('waiting');
return result + ' 2';
}

Categories