MII
MII

Reputation: 969

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))).

Upvotes: 1

Views: 491

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074138

Your first example works like this:

  1. new Promise runs, calling the function you pass it (the executor function) synchronously
  2. 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
  3. new Promise returns the promise
  4. 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).
  5. 1000ms or so later, the browser queues a call to the setTimeout callback, which the JavaScript engine picks up and runs
  6. The callback calls the resolve function to fulfill the promise with the value 1
  7. 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.

Upvotes: 1

Related Questions