Javascript: Awaiting resolved promise - javascript

I would like to know, if awaiting a resolved promise, will lead to a synchronous code execution or may lead to asynchronous one.
I made this little snippet to check on browsers :
const promise = new Promise((resolve) => {
console.log('exec promise');
setTimeout(() => {
console.log('executed promise');
resolve();
}, 1000);
});
(async () => {
console.log('start');
for (let i = 0; i < 1e8; i += 1) {
await promise;
}
console.log('end');
})();
And it looks like browsers make it synchronous (considering the screen freeze).
BUT ... is it due to browser specific implementation ? Or by design ?

Here's an alternative demo that doesn't freeze the browser (which I think is getting the way of what we're trying to show), and which shows the opposite behaviour to your initial conclusion:
const p = new Promise((r) => r());
p.then(() => {
(async() => {
console.log('a');
await p;
console.log('p');
})()
console.log('b');
});
In this case, we get hold of p, a resolved promise. We then kick off an async function that awaits it. The output is:
a
b
p
IE the async function still returned control back (and b was logged) when it hit the await on the already resolved p. Only after b was logged and the event loop was free to return execution back to after the await was p subsequently logged.
Is this standard behaviour? Yes.
The specification effectively turns the awaited expression to the then part of a promise, and in steps 9 and 10 of a subsequent step makes the decision on how to proceed.
If promise.[[PromiseState]] is pending, then
a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
b. Append rejectReaction as the last element of the List that is promise.[PromiseRejectReactions]].
Else if promise.[[PromiseState]] is fulfilled,
a. then Let value be promise.[[PromiseResult]].
b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value)
c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).
Step 9 says if it's pending, to add the generated then to the list of things to be called when the promise is fulfilled.
Step 10 gets to the heart of your question - it says if it's fulfilled already, to enqueue the job - ie to place it on the queue of things to be called back.
The spec effectively says that an await should never return synchronously.

This is expected behavior. You freeze the event loop there, because the promise has already been resolved, the next await operator will wait&resolve for the next promise descriptor on the same iteration of the event loop. But this behavior can be platform-specific, depending on which promise implementation you're actually using - native or shim.
const promise= Promise.resolve();
(async()=>{
for(let i=0; i< 10000000; i++) {
await promise;
}
console.log('end');
})()
setTimeout(()=> console.log('nextEventLoopTick'), 0);
Will output the follwing:
end
nextEventLoopTick
As you can see, the async fn will be fully resolved before setTimeout. So, this indicates that the async function is resolved on the same eventloop tick.

Related

Determine promise state using Promise.race and setImmediate

TL;DR: Will already resolved promises always beat setImmediate in a race?
Background:
Sometimes you want to know if a promise is resolved without awaiting its completion. There are some old legacy tricks like using util.inspect to get the internal state and checking if it contains the string "<pending>". However, a more stable solution is to use Promise.race() to essentially wait for your unknown promise with a (very small) timeout.
const p = doSomething();
const result = await Promise.race([
p,
new Promise(resolve => setTimeout(resolve, 1))
]);
if (result) {
// p was resolved within 1 ms!
...
}
This will at most wait 1 ms to get result, which will then either contain the resolved value of p or undefined.
If required, the "timeout promise" may of course return something different than undefined to distinguish actual undefined values returned from doSomething():
const PENDING = Symbol.for('PENDING');
const result = await Promise.race([
p,
new Promise(resolve => setTimeout(() => resolve(PENDING), 1))
]);
if (result !== PENDING) {
...
}
Now we'll either get the resolved value from doSomething() or the unique symbol PENDING.
Now to my question. In addition to setTimeout, there's a setImmediate function that basically behaves like a timer that expires immediately; it just gives the event loop a go before resolving. When using this in my Promise.race expression above, empirically it seems to work, i.e., if p is already resolved it will beat setImmediate in a race, but I'm wondering if there is any such guarantee - or if I should be using a timer of 1 ms to guarantee that a resolved promise beats the timer?
I've tried putting p before and after the setImmediate promise in the array passed to Promise.race and it worked both ways, but still I'm worried it might behave randomly or be OS dependent? Or is the fact that setImmediate waits for one round of I/O enough to guarantee that any resolved promises will win?
From the documentation:
Schedules the "immediate" execution of the callback after I/O events' callbacks
Edit:
I found that even this "seems" to work:
const result = await Promise.race([p, new Promise(resolve => resolve(PENDING))]);
or actually even this:
const result = await Promise.race([p, Promise.resolve(PENDING)]);
However, here the order is important. If p is resolved and is before the timeout promise, it will win, but if it's after the timeout promise in the array it will lose.
The question is the same though: is this approach guaranteed to let p win if it's already resolved?
Considering the codes below in Nodejs
Ref. https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
setImmediate(() => console.log("setImmediate"));
setTimeout(() => console.log("setTimeout"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("Promise"));
console.log("sync");
Output:
You can notice the sequence and that's how it is executed in order.
sync
nextTick
Promise
setTimeout
setImmediate
To answer your question, we can wrap the codes in Promises like below:
(async function main() {
const result = await Promise.race([
new Promise(resolve => resolve("sync")),
new Promise(resolve => setImmediate(() => resolve("setImmediate"))),
new Promise(resolve => setTimeout(() => resolve("setTimeout"))),
new Promise(resolve => Promise.resolve().then(() => resolve("Promise"))),
new Promise(resolve => process.nextTick(() => resolve("nextTick"))),
]);
console.log({ result });
})();
As sync function is to be executed first, so it would be returned.
Output:
{ result: 'sync' }
You can comment one of the Promise above to see which one resolve first.

Confusion with how JS engine runs promises?

I am new to JS and was learning promises. So, let's say we have this code:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
})
As you can see the code above, when promise is invoked, setTimeout is run via callback queue. The question is When setTimeOut is sent to a browser, will JS engine omit .then() and continues running the rest of the code until the promise resolves? Secondly, async/await example:
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
When showAvatar function is called, JS engine will encounter let response = await fetch('/article/promise-chaining/user.json'); and sends fetch to the browser to handle. The second question is Will JS engine wait until fetch gets resolved or Will JS engine continue executing let user = await response.json(); and the rest of the code inside showAvatar function? If so, how can JS engine handle response.json() since response is not received? Hope you got my point))).
Your first example works like this:
new Promise runs, calling the function you pass it (the executor function) synchronously
Code in the executor function calls setTimeout, passing in a function to call 1000ms later; the browser adds that to its list of pending timer callbacks
new Promise returns the promise
then is called, adding the function you pass into it to the promise's list of fulfillment handlers and creating a new promise (which your code doesn't use, so it gets thrown away).
1000ms or so later, the browser queues a call to the setTimeout callback, which the JavaScript engine picks up and runs
The callback calls the resolve function to fulfill the promise with the value 1
That triggers the promise's fulfillment handlers (asynchronously, but it doesn't really matter for this example), so the handler attached in Step 4 gets called, showing the alert and then returning result * 2 (which is 1 * 2, which is 1). That value is used to fulfill the promise created and thrown away in Step 4.
Will JS engine wait until fetch gets resolved or Will JS engine continue executing let user = await response.json();...
It waits. The async function is suspended at the await in await fetch(/*...*/), waiting for the promise fetch returned to settle. While it's suspended, the main JavaScript thread can do other things. Later, when the promise settles, the function is resumed and either the fulfillment value is assigned to response (if the promise is fulfilled) or an exception will get thrown (if it is rejected).
More generally: async functions are synchronous up until the first await or return in their code. At that point, they return their promise, which is settled later based on the remainder of the async function's code.
In a comment you asked:
when async function is suspended at each await, will async function is removed from the call stack and is put back to the call stack again when the promise being awaited settles?
At a low level, yes; but to make debugging easier, a good, up-to-date JavaScript engine maintains an "async call stack" they use for error traces and such. For instance, if you run this on Chrome...
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function inner() {
await delay(80);
throw new Error("boom");
}
async function outer() {
await inner();
}
function wrapper() {
outer()
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error.stack);
});
}
wrapper();
...the stack looks like this:
Error: boom
at inner (https://stacksnippets.net/js:18:11)
at async outer (https://stacksnippets.net/js:22:5)
Notice the "async" prior to "outer," and also notice that wrapper isn't mentioned anywhere. wrapper is done and has returned, but the async functions were suspended and resumed.

Might a promise be garbage collected too early without a .then() in JavaScript

So basically I want to asynchronously execute 2 synchronous promises. Like so:
(function foo() {
var newValues = ...;
this.getMessages().then((msgs: Array<any>) => {
this.saveMessages(msgs.concat(newValues));
});
})()
Now since I don't want to wait for saveMessages to finish execution I didn't add a .then() at the end. Since I don't care about the result. I just want to async function to execute so at some point we have the messages offline.
BUT I fear that the promise might get garbage collected since by the standard (as far as I know) you always need a .then for promises.
So my question is whether I need to add an empty .then to make sure it's not garbage collected too early and the function wouldn't execute correctly. Like so: this.saveMessages(msgs.concat(newValues)).then();?
And is it the same in the browser and NodeJS?
BUT I fear that the promise might get garbage collected since by the
standard (as far as I know) you always need a .then() for promises.
No. There does not exist any such standard -- to my knowledge -- which states that the GC will free up the memory allocated to the a Promise object if a .then() is not attached to it.
Promises will get executed regardless of whether a .then() -- or a .catch() for that matter -- is attached to it.
So my question is whether I need to add an empty .then() to make sure
it's not garbage collected too early and the function wouldn't execute
correctly.
No you do not need an empty .then()
And is it the same in the browser and NodeJS?
Yes, as long as you use same Promise implementation on both the runtimes.
So, you would be completely fine with this:
function foo() {
var newValues = ...;
this.getMessages().then((msgs: Array<any>) => {
this.saveMessages(msgs.concat(newValues));
});
}
But, You should consider attaching a .catch() to it to handle any errors. If you don't, you will have UnhandledPromiseRejectionWarning. In future versions of Node, it can crash your whole app. Read about it here
EDIT
Even if the parent has nothing to process or execute, the program will wait till each of the 100 promises has resolved. Try this:
const n = 100;
const sleep = (id, seconds) =>
new Promise(resolve => {
setTimeout(() => {
console.log(`promise with id ${id} is resolving`);
resolve();
}, seconds * 1000);
});
for (let i = 0; i < n; i++) {
sleep(i, 1 * i);
}
console.log("Last line of parent");
No, the Promise knows nothing about how you're handling its completion until it gets there.
You don't need to worry about garbage collection preemptively, Javascript is smarter than that, but you do have to worry about the global execution context completing, as in, your program closing the connection, before the Promise has resolved, which would then cancel the promise and perform garbage collection.
Since you're running it asynchronously, if you want to guarantee the program awaits the completion of that Promise without making everything synchronous you can always hold the completion state of the Promises on a scoped variable or use an event emitter...
var imDone = false;
function foo() {
var newValues = ...;
this.getMessages().then((msgs: Array<any>) => {
this.saveMessages(msgs.concat(newValues))
.catch(err=> { console.log(err); imDone = true; })
.then(()=> imDone = true);
}).catch(err=> console.log(err));
}
....
if (imDone) {
// finish execution, res.send() or res.end() in Node
}
you can use this code and return your first promise and second promise is run sync.
function foo() {
var newValues = ...;
return this.getMessages().then((msgs: Array<any>) => {
this.saveMessages(msgs.concat(newValues));
});
}
You are not executing the code in the right way. Promises should be handled in the right way.There is no issue with the garbage collector freeing up the space. Your code can be written in this manner which would increase the readability and make the code easier to understand.
// This is pseudo synchronous execution of the code
function foo() {
var newValues = 'testing';
return this.getMessages()
.then((msgs: Array<any>) => {
// here return is important
return this.saveMessages(msgs.concat(newValues));
});
}
You can reduce the complexity by using async/await feature:
aync function foo() {
try {
var newValues = 'testing';
const msgs = await this.getMessages()
await this.saveMessages(msgs.concat(newValues))
} catch (ex) {
// handle your error here
}
}

Async function not returning value, but console.log() does: how to do? [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I have an es6 class, with an init() method responsible for fetching data, transforming it, then update the class's property this.data with newly transformed data.
So far so good.
The class itself has another getPostById() method, to just do what it sounds like. Here is the code for the class:
class Posts {
constructor(url) {
this.ready = false
this.data = {}
this.url = url
}
async init() {
try {
let res = await fetch( this.url )
if (res.ok) {
let data = await res.json()
// Do bunch of transformation stuff here
this.data = data
this.ready = true
return data
}
}
catch (e) {
console.log(e)
}
}
getPostById(id){
return this.data.find( p => p.id === id )
}
}
Straightforward, except I have an async/await mechanism in the init() method.
Now, this code will work correctly:
let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts')
allPosts.init()
.then( d => console.log(allPosts.getPostById(4)) )
// resulting Object correctly logged in console
but it only gets printed into the console:
How could I use allPosts.getPostById(4) as a return of a function ?
Like:
let myFunc = async () => {
const postId = 4
await allPosts.init() // I need to wait for this to finish before returning
// This is logging correct value
console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) )
// How can I return the RESULT of allPosts.getPostById( postId ) ???
return allPosts.getPostById( postId )
}
myFunc() returns a Promise but not the final value. I have read several related posts on the subject but they all give example of logging, never returning.
Here is a fiddle that includes two ways of handling init(): using Promise and using async/await. No matter what I try, I can't manage to USE the FINAL VALUE of getPostById(id).
The question of this post is: how can I create a function that will RETURN the VALUE of getPostById(id) ?
EDIT:
A lot of good answers trying to explain what Promises are in regards to the main execution loop.
After a lot of videos and other good reads, here is what I understand now:
my function init() correctly returns. However, within the main event loop: it returns a Promise, then it is my job to catch the result of this Promise from within a kinda parallel loop (not a new real thread). In order to catch the result from the parallel loop there are two ways:
use .then( value => doSomethingWithMy(value) )
use let value = await myAsyncFn(). Now here is the foolish hiccup:
await can only be used within an async function :p
thus itself returning a Promise, usable with await which should be embed in an async function, which will be usable with await etc...
This means we cannot really WAIT for a Promise: instead we should catch parallel loop indefinitely: using .then() or async/await.
Thanks for the help !
As for your comment; I'll add it as answer.
The code you write in JavaScript is run on one thread, that means that if your code could actually wait for something it will block any of your other code from getting executed. The event loop of JavaScript is explained very well in this video and if you like to read in this page.
A good example of blocking code in the browser is alert("cannot do anything until you click ok");. Alert blocks everything, the user can't even scroll or click on anything in the page and your code also blocks from executing.
Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
x=>console.log(
"does not resolve untill you click ok on the alert:",
x
)
);
Run that in a console and you see what I mean by blocking.
This creates a problem when you want to do something that takes time. In other frameworks you'd use a thread or processes but there is no such thing in JavaScript (technically there is with web worker and fork in node but that's another story and usually far more complicated than using async api's).
So when you want to make a http request you can use fetch but fetch takes some time to finish and your function should not block (has to return something as fast as possible). This is why fetch returns a promise.
Note that fetch is implemented by browser/node and does run in another thread, only code you write runs in one thread so starting a lot of promises that only run code you write will not speed up anything but calling native async api's in parallel will.
Before promises async code used callbacks or would return an observable object (like XmlHttpRequest) but let's cover promises since you can convert the more traditional code to a promise anyway.
A promise is an object that has a then function (and a bunch of stuff that is sugar for then but does the same), this function takes 2 parameters.
Resolve handler: A function that will be called by the promise when the promise resolves (has no errors and is finished). The function will be passed one argument with the resolve value (for http requests this usually is the response).
Reject handler: A function that will be called by the promise when the promise rejects (has an error). This function will be passed one argument, this is usually the error or reason for rejection (can be a string, number or anything).
Converting callback to promise.
The traditional api's (especially nodejs api's) use callbacks:
traditionalApi(
arg
,function callback(err,value){
err ? handleFail(err) : processValue(value);
}
);
This makes it difficult for the programmer to catch errors or handle the return value in a linear way (from top to bottom). It gets even more impossible to try and do things parallel or throttled parallel with error handling (impossible to read).
You can convert traditional api's to promises with new Promise
const apiAsPromise = arg =>
new Promise(
(resolve,reject)=>
traditionalApi(
arg,
(err,val) => (err) ? reject(err) : resolve(val)
)
)
async await
This is what's called syntax sugar for promises. It makes promise consuming functions look more traditional and easier to read. That is if you like to write traditional code, I would argue that composing small functions is much easier to read. For example, can you guess what this does?:
const handleSearch = search =>
compose([
showLoading,
makeSearchRequest,
processRespose,
hideLoading
])(search)
.then(
undefined,//don't care about the resolve
compose([
showError,
hideLoading
])
);
Anayway; enough ranting. The important part is to understand that async await doesn't actually start another thread, async functions always return a promise and await doesn't actually block or wait. It's syntax sugar for someFn().then(result=>...,error=>...) and looks like:
async someMethod = () =>
//syntax sugar for:
//return someFn().then(result=>...,error=>...)
try{
const result = await someFn();
...
}catch(error){
...
}
}
The examples allways show try catch but you don't need to do that, for example:
var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
x=>console.log("never happens, doesn't resolve")
,err=>console.warn("got rejected:",err)
);
Any error thrown or await returning a rejected promise will cause the async function to return a rejected promise (unless you try and catch it). Many times it is desirable to just let it fail and have the caller handle errors.
Catching errors could be needed when you want the promise to succeed with a special value for rejected promises so you can handle it later but the promise does not technically reject so will always resolve.
An example is Promise.all, this takes an array of promises and returns a new promise that resolves to an array of resolved values or reject when any one of them rejects. You may just want to get the results of all promises back and filter out the rejected ones:
const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
urls.map(//map array of urls to array of promises that don't reject
url =>
fetch(url)
.then(
undefined,//do not handle resolve yet
//when you handle the reject this ".then" will return
// a promise that RESOLVES to the value returned below (new Fail([url,err]))
err=>new Fail([url,err])
)
)
)
.then(
responses => {
console.log("failed requests:");
console.log(
responses.filter(//only Fail type
isFail
)
);
console.log("resolved requests:");
console.log(
responses.filter(//anything not Fail type
response=>!isFail(response)
)
);
}
);
Your question and the comments suggest you could use a little intuition nudge about the way the event loop works. It really is confusing at first, but after a while it becomes second nature.
Rather than thinking about the FINAL VALUE, think about the fact that you have a single thread and you can't stop it — so you want the FUTURE VALUE -- the value on the next or some future event loop. Everything you write that is not asynchronous is going to happen almost immediately — functions return with some value or undefined immediately. There's nothing you can do about. When you need something asynchronously, you need to setup a system that is ready to deal with the async values when they return sometime in the future. This is what events, callbacks, promises (and async/await) all try to help with. If some data is asynchronous, you simply can not use it in the same event loop.
So what do you do?
If you want a pattern where you create an instance, call init() and then some function that further process it, you simply need to setup a system that does the processing when the data arrives. There are a lot of ways to do this. Here's one way that's a variation on your class:
function someAsync() {
console.log("someAsync called")
return new Promise(resolve => {
setTimeout(() => resolve(Math.random()), 1000)
})
}
class Posts {
constructor(url) {
this.ready = false
this.data = "uninitilized"
this.url = url
}
init() {
this.data = someAsync()
}
time100() {
// it's important to return the promise here
return this.data.then(d => d * 100)
}
}
let p = new Posts()
p.init()
processData(p)
// called twice to illustrate point
processData(p)
async function processData(posts) {
let p = await posts.time100()
console.log("randomin * 100:", p)
}
init() saves the promise returned from someAsync(). someAsync() could be anything that returns a promise. It saves the promise in an instance property. Now you can call then() or use async/await to get the value. It will either immediately return the value if the promise has already resolved or it will deal with it when it has resolved. I called processData(p) twice just to illustrate that it doesn't calle the someAsync() twice.
That's just one pattern. There are a lot more — using events, observables, just using then() directly, or even callbacks which are unfashionable, but still can be useful.
NOTE: Wherever you use await it has to be inside an async function.
Check out the UPDATED FIDDLE
You need to use await myFunc() to get the value you expect from getPostById because an async function always returns a promise.
This sometimes is very frustrating as the whole chain needs to be converted into async functions but that's the price you pay for converting it to a synchronous code, I guess. I am not sure if that can be avoided but am interested in hearing from people who have more experience on this.
Try out the below code in your console by copying over the functions and then accessing final and await final.
NOTE:
An async function CAN contain an await expression.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
There is no rule that is must have await in order to even declare an async function.
The example below uses an async function without await just to show that an async function always returns a promise.
const sample = async () => {
return 100;
}
// sample() WILL RETURN A PROMISE AND NOT 100
// await sample() WILL RETURN 100
const init = async (num) => {
return new Promise((resolve, reject) => {
resolve(num);
});
}
const myFunc = async (num) => {
const k = await init(num);
return k;
}
// const final = myFunc();
// final; This returns a promise
// await final; This returns the number you provided to myFunc

In JavaScript, does using await inside a loop block the loop?

Take the following loop:
for(var i=0; i<100; ++i){
let result = await some_slow_async_function();
do_something_with_result();
}
Does await block the loop? Or does the i continue to be incremented while awaiting?
Is the order of do_something_with_result() guaranteed sequential with regard to i? Or does it depend on how fast the awaited function is for each i?
Does await block the loop? Or does the i continue to be incremented while awaiting?
"Block" is not the right word, but yes, i does not continue to be incremented while awaiting. Instead the execution jumps back to where the async function was called, providing a promise as return value, continuing the rest of the code that follows after the function call, until the code stack has been emptied. Then when the awaiting is over, the state of the function is restored, and execution continues within that function. Whenever that function returns (completes), the corresponding promise -- that was returned earlier on -- is resolved.
Is the order of do_something_with_result() guaranteed sequential with regard to i? Or does it depend on how fast the awaited function is for each i?
The order is guaranteed. The code following the await is also guaranteed to execute only after the call stack has been emptied, i.e. at least on or after the next microtask can execute.
See how the output is in this snippet. Note especially where it says "after calling test":
async function test() {
for (let i = 0; i < 2; i++) {
console.log('Before await for ', i);
let result = await Promise.resolve(i);
console.log('After await. Value is ', result);
}
}
test().then(_ => console.log('After test() resolved'));
console.log('After calling test');
As #realbart says, it does block the loop, which then will make the calls sequential.
If you want to trigger a ton of awaitable operations and then handle them all together, you could do something like this:
const promisesToAwait = [];
for (let i = 0; i < 100; i++) {
promisesToAwait.push(fetchDataForId(i));
}
const responses = await Promise.all(promisesToAwait);
You can test async/await inside a "FOR LOOP" like this:
(async () => {
for (let i = 0; i < 100; i++) {
await delay();
console.log(i);
}
})();
function delay() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 100);
});
}
async functions return a Promise, which is an object that will eventually "resolve" to a value, or "reject" with an error. The await keyword means to wait until this value (or error) has been finalized.
So from the perspective of the running function, it blocks waiting for the result of the slow async function. The javascript engine, on the other hand, sees that this function is blocked waiting for the result, so it will go check the event loop (ie. new mouse clicks, or connection requests, etc.) to see if there are any other things it can work on until the results are returned.
Note however, that if the slow async function is slow because it is computing lots of stuff in your javascript code, the javascript engine won't have lots of resources to do other stuff (and by doing other stuff would likely make the slow async function even slower). Where the benefit of async functions really shine is for I/O intensive operations like querying a database or transmitting a large file where the javascript engine is well and truly waiting on something else (ie. database, filesystem, etc.).
The following two bits of code are functionally equivalent:
let result = await some_slow_async_function();
and
let promise = some_slow_async_function(); // start the slow async function
// you could do other stuff here while the slow async function is running
let result = await promise; // wait for the final value from the slow async function
In the second example above the slow async function is called without the await keyword, so it will start execution of the function and return a promise. Then you can do other things (if you have other things to do). Then the await keyword is used to block until the promise actually "resolves". So from the perspective of the for loop it will run synchronous.
So:
yes, the await keyword has the effect of blocking the running function until the async function either "resolves" with a value or "rejects" with an error, but it does not block the javascript engine, which can still do other things if it has other things to do while awaiting
yes, the execution of the loop will be sequential
There is an awesome tutorial about all this at http://javascript.info/async.
No Event loop isn't blocked, see example below
function sayHelloAfterSomeTime (ms) {
return new Promise((resolve, reject) => {
if (typeof ms !== 'number') return reject('ms must be a number')
setTimeout(() => {
console.log('Hello after '+ ms / 1000 + ' second(s)')
resolve()
}, ms)
})
}
async function awaitGo (ms) {
await sayHelloAfterSomeTime(ms).catch(e => console.log(e))
console.log('after awaiting for saying Hello, i can do another things ...')
}
function notAwaitGo (ms) {
sayHelloAfterSomeTime(ms).catch(e => console.log(e))
console.log('i dont wait for saying Hello ...')
}
awaitGo(1000)
notAwaitGo(1000)
console.log('coucou i am event loop and i am not blocked ...')
Here is my test solution about this interesting question:
import crypto from "crypto";
function diyCrypto() {
return new Promise((resolve, reject) => {
crypto.pbkdf2('secret', 'salt', 2000000, 64, 'sha512', (err, res) => {
if (err) {
reject(err)
return
}
resolve(res.toString("base64"))
})
})
}
setTimeout(async () => {
console.log("before await...")
const a = await diyCrypto();
console.log("after await...", a)
}, 0);
setInterval(() => {
console.log("test....")
}, 200);
Inside the setTimeout's callback the await blocks the execution. But the setInterval is keep runnning, so the Event Loop is running as usual.
Let me clarify a bit because some answers here have some wrong information about how Promise execution works, specifically when related to the event loop.
In the case of the example, await will block the loop. do_something_with_result() will not be called until await finishes it's scheduled job.
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await#handling_asyncawait_slowdown
As for the other points, Promise "jobs" run before the next event loop cycle, as microtasks. When you call Promise.then() or the resolve() function inside new Promise((resolve) => {}), you creating a Job. Both await and async are wrapper, of sorts, for Promise, that will both create a Job. Microtasks are meant to run before the next event loop cycle. That means adding a Promise Job means more work before it can move on to the next event loop cycle.
Here's an example how you can lock up your event loop because your promises (Jobs) take too long.
let tick = 0;
let time = performance.now();
setTimeout(() => console.log('Hi from timeout'), 0);
const tock = () => console.log(tick++);
const longTask = async () => {
console.log('begin task');
for(let i = 0; i < 1_000_000_000; i++) {
Math.sqrt(i);
}
console.log('done task');
}
requestAnimationFrame(()=> console.log('next frame after', performance.now() - time, 'ms'));
async function run() {
await tock();
await tock();
await longTask(); // Will stall your UI
await tock(); // Will execute even though it's already dropped frames
await tock(); // This will execute too
}
run();
// Promise.resolve().then(tock).then(tock).then(longTask).then(tock).then(tock);
In this sample, 5 total promises are created. 2 calls for tock, 1 for longTask and then 2 calls for tock. All 5 will run before the next event loop.
The execution would be:
Start JS execution
Execute normal script
Run 5 scheduled Promise jobs
End JS execution
Event Loop Cycle Start
Request Animation Frame fire
Timeout fire
The last line commented line is scheduling without async/await and results in the same.
Basically, you will stall the next event loop cycle unless you tell your JS execution where it can suspend. Your Promise jobs will continue to run in the current event loop run until it finishes its call stack. When you call something external, (like fetch), then it's likely using letting the call stack end and has a callback that will resolve the pending Promise. Like this:
function waitForClick() {
return new Promise((resolve) => {
// Use an event as a callback;
button.onclick = () => resolve();
// Let the call stack finish by implicitly not returning anything, or explicitly returning `undefined` (same thing).
// return undefined;
})
}
If you have a long job job that want to complete, either use a Web Worker to run it without pausing, or insert some pauses with something like setTimeout() or setImmediate().
Reshaping the longTask function, you can do something like this:
const longTask = async () => {
console.log('begin task');
for(let i = 0; i < 1_000_000_000; i++)
if (i && i % (10_000_000) === 0) {
await new Promise((r) => setTimeout(r,0));
}
Math.sqrt(i);
console.log('done task');
}
Basically, instead of doing 1 billion records in one shot, you only do 10 million and then wait until the next event (setTimeout) to run the next one. The bad here is it's slower because of how much you hand back to the event loop. Instead, you can use requestIdleCallback() which is better, but still not as good as multi-threading via Web Workers.
But be aware that just slapping on await or Promise.resolve().then() around a function won't help with the event loop. Both will wait until the function returns with either a Promise or a value before letting up for the event loop. You can mostly test by checking to see if the function you're calling returns an unresolved Promise immediately.
Does await block the loop? Or does the i continue to be incremented while awaiting?
No, await won't block the looping. Yes, i continues to be incremented while looping.
Is the order of do_something_with_result() guaranteed sequential with regard to i? Or does it depend on how fast the awaited function is for each i?
Order of do_something_with_result() is guaranteed sequentially but not with regards to i. It depends on how fast the awaited function runs.
All calls to some_slow_async_function() are batched, i.e., if do_something_with_result() was a console then we will see it printed the number of times the loop runs. And then sequentially, after this, all the await calls will be executed.
To better understand you can run below code snippet:
async function someFunction(){
for (let i=0;i<5;i++){
await callAPI();
console.log('After', i, 'th API call');
}
console.log("All API got executed");
}
function callAPI(){
setTimeout(()=>{
console.log("I was called at: "+new Date().getTime())}, 1000);
}
someFunction();
One can clearly see how line console.log('After', i, 'th API call'); gets printed first for entire stretch of the for loop and then at the end when all code is executed we get results from callAPI().
So if lines after await were dependent on result obtained from await calls then they will not work as expected.
To conclude, await in for-loop does not ensure successful operation on result obtained from await calls which might take some time to finish.
In node, if one uses neo-async library with waterfall, one can achieve this.

Categories

Resources