2

I have trouble understanding the whole concept of async/await and have spent hours reading the documentation and trying to get an example to work. But I still do not understand why this tiny example does not work.

The result I want is for document.body.innerHTML to contain the text "123", since doFirst() should finish running before doSomething() finishes.

But I always get "132", as if I haven’t used async/await at all. What am I doing wrong?

async function doFirst() {
  document.body.innerHTML = document.body.innerHTML + "1";
  setTimeout(function() {
    document.body.innerHTML = document.body.innerHTML + "2";
  }, 500);
}

async function doSomething()
{
  await doFirst();
  document.body.innerHTML = document.body.innerHTML + "3";
}

doSomething();

Sorry for posting this again, last time I posted this question it was marked as a duplicate. So this time I want to clarify that I’m not looking for any answers that show me how to do it properly but also for explanations on why my code does not work.

Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
Petra1999
  • 93
  • 1
  • 8
  • 2
    `setTimeout` is callback-based, not Promise-based. You need to convert it to a Promise so you can return it from `doFirst`, so that the `doSomething` can consume it as a Promise – CertainPerformance Apr 11 '20 at 08:20
  • 3
    @CertainPerformance, this user is looking for an explanation, not a "fix this code". This is not a duplicate of that question and it doesn't help them much. This should be re-opened. – Jim Apr 11 '20 at 08:34

3 Answers3

4

That is because the doFirst() returns a promise that is resolved before the timer runs out. Contrary to your expectations, setTimeout does not pause script execution.

In order to ensure that doFirst() resolves after the timer runs out, you will need to wrap the entire function inside a Promise object. The timer will call the resolve() method once it runs out, as such:

function doFirst() {
  return new Promise(resolve => {
    document.body.innerHTML = document.body.innerHTML + "1";
    setTimeout(function() {
      document.body.innerHTML = document.body.innerHTML + "2";
      resolve();
    }, 500);
  });
}

async function doSomething()
{
  await doFirst();
  document.body.innerHTML = document.body.innerHTML + "3";
}

doSomething();

If you want to avoid callback hell, an cleaner way is to simply create a utility function that resolves a promise after a setTimeout, then you can await on the promise returned from that function to be resolved before printing "2" into the DOM:

// Utility function that resovles a promise after a given duration
function sleep(duration) {
  return new Promise(resolve => setTimeout(resolve, duration));
}

async function doFirst() {
  document.body.innerHTML = document.body.innerHTML + "1";
  await sleep(500);
  document.body.innerHTML = document.body.innerHTML + "2";
}

async function doSomething()
{
  await doFirst();
  document.body.innerHTML = document.body.innerHTML + "3";
}

doSomething();
Terry
  • 63,248
  • 15
  • 96
  • 118
2

Take a look at below code:


async function doFirst() {
  document.body.innerHTML = document.body.innerHTML + "1";
  return myAsyncFunc(500)
}

function myAsyncFunc(delay) {
    return new Promise(resolve => setTimeout(function() {
            document.body.innerHTML = document.body.innerHTML + "2"
            resolve();
        }, delay));
}

async function doSomething()
{
  await doFirst();
  document.body.innerHTML = document.body.innerHTML + "3";
}

What are you doing wrong ?

You are not returning a promise in doFirst() , so the operation is not asynchronus (in short, your code doSomething() does not wait for doFirst() to finish up and move to line document.body.innerHTML = document.body.innerHTML + "3";)

Note:

  1. How document.body.innerHTML = document.body.innerHTML + "1"; is run immediately and the myAsyncFunc is returned (which basically returns a Promise).

  2. After 500ms , setTimeout executes and hence document.body.innerHTML = document.body.innerHTML + "2" is run. After that, the next line return resolve() is returned , which marks the Promise to be completed.

  3. The await is over as soon as resolve is returned and so the "3" is added

Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
  • “You are now returning a promise in `doFirst()`[…]” – I think you mean “not” instead of “now” though that’s not true. `doFirst` does indeed return a promise, just one that does not wait for the timeout in order to resolve. – Raphael Schweikert Apr 11 '20 at 15:50
0

I think you may have a misconception about what the async keyword does. It does not magically change the function to be asynchronous, despite its name. It only does the following two things:

  1. Allow await to be used inside the function body.
  2. Ensure the function returns a Promise.

But only when those two are used in concert is it actually useful. An async function without await in its body does not need to be async at all.

Your example function doFirst does not use await, so it’s essentially equivalent to the following:

function doFirst() {
    document.body.innerHTML = document.body.innerHTML + "1";
    setTimeout(function() {
        document.body.innerHTML = document.body.innerHTML + "2";
    }, 500);
    return Promise.resolve(undefined);
}

As you can see, there’s nothing asynchronous about this code. Promise.resolve returns a promise that can immediately resolve to the given value. This means using await doFirst() in doSomething will not actually await anything as the promise is already resolved.

What you want for doFirst to do is return a promise that won’t resolve until the setTimeout callback has executed. Unfortunately setTimeout does not return a promise so you’ll need to create one manually:

function doFirst() {
    document.body.innerHTML = document.body.innerHTML + "1";
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            document.body.innerHTML = document.body.innerHTML + "2";
            resolve();
        }, 500);
    });
}

You may notice that you don’t need the async keyword here. That’s because the function does not use await and already returns a promise. doSomething will still be able to use await doFirst() because await works with any promise, not just when calling async functions.

Raphael Schweikert
  • 18,244
  • 6
  • 55
  • 75