Shouldn't async return a Promise without explicitly wrapping the return value in a Promise?

MDN documentation states that

When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.

Therefore, what am I doing wrong, considering that await expects a resolved promise?

foo();

async function foo(){
    await bar();
    zoo();
}

async function bar(){
    setTimeout(() => {
        console.log("bar");
        return;
    }, 1000);    
}

function zoo(){
    console.log("zoo");    
}

According to what I (wrongly) understood, it should first log bar and then zoo, but it's logging the other way around.

EDIT: Now, thanks to clarifications from @Matt Morgan, I understand the mistake, as the bar() function returns undefined. Nonetheless, I thought that by calling an async function per se alone would make the function return immediately an unresolved promise and that such promise would be resolved when the async function would return any value (even undefined). But I now realise one really needs to return a promise oneself by declaring it with return Promise statement. I think the MDN article for async, which I read it all, is a bit confusing on this topic (just my opinion).

Therefore I could simply amend my bar() function to:

function bar(){
    return new Promise(function(resolve){
        setTimeout(() => {
            console.log("bar");
            resolve();
        }, 1000);
    });
}

Upvotes: 1

Views: 1487

Answers (1)

Matt Morgan
Matt Morgan

Reputation: 5303

bar() is setting the timeout and returning undefined, which is not the same as immediately running the logging statement that runs when the timeout finishes.

So, zoo() runs, and then when the timeout finishes (1000ms later), you see "bar" in the console.

Here's a modified example without the timeout:

foo();

async function foo(){
    await bar();
    zoo();
}

async function bar(){
   console.log("bar");   
}

function zoo(){
    console.log("zoo");    
}

With no setTimeout, you get the order of execution you expect.

A second example, where you have a delay() function that wraps setTimeout in a promise:

foo();

async function foo(){
    await bar();
    zoo();
}

async function bar(){
   await delay();
   console.log("bar");   
}

function delay(t, v) {
   return new Promise(function(resolve) { 
       setTimeout(resolve.bind(null, v), t)
   });
}

function zoo(){
    console.log("zoo");    
}

The final snippet does wait for the resolution of the promise, so you see bar, then foo.

The above delay was taken from https://stackoverflow.com/a/39538518/3084820

Here's a final example, where bar() returns "bar". This means it gets wrapped in a promise by the async declaration, and resolved by the await inside foo() Again, you see what you expected to see.

foo();

async function foo(){
    console.log(await bar());
    zoo();
}

async function bar(){
    return 'bar';   
}

function zoo(){
    console.log("zoo");    
}

It's important to understand the your original example does not return the value bar. It returns undefined, and if you change your original code to log out the returned value of bar(), you would see three things in the console:

  1. The immediately returned promise form bar() that resolves to undefined.
  2. The value zoo from the zoo() function.
  3. Finally, after 1000ms elapsed, you would see the bar logged from the setTimeout that got shoved on the queue.

Try it, see what you get.

Upvotes: 3

Related Questions