Search code examples
javascriptnode.jsasync-awaitecmascript-next

async function implicitly returns promise?


I read that async functions marked by the async keyword implicitly return a promise:

async function getVal(){
 return await doSomethingAync();
}

var ret = getVal();
console.log(ret);

but that is not coherent...assuming doSomethingAsync() returns a promise, and the await keyword will return the value from the promise, not the promise itsef, then my getVal function should return that value, not an implicit promise.

So what exactly is the case? Do functions marked by the async keyword implicitly return promises or do we control what they return?

Perhaps if we don't explicitly return something, then they implicitly return a promise...?

To be more clear, there is a difference between the above and

function doSomethingAync(charlie) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(charlie || 'yikes');
        }, 100);
    })
}

async function getVal(){
   var val = await doSomethingAync();  // val is not a promise
   console.log(val); // logs 'yikes' or whatever
   return val;  // but this returns a promise
}

var ret = getVal();
console.log(ret);  //logs a promise

In my synopsis the behavior is indeed inconsistent with traditional return statements. It appears that when you explicitly return a non-promise value from an async function, it will force wrap it in a promise. I don't have a big problem with it, but it does defy normal JS.


Solution

  • The return value will always be a promise. If you don't explicitly return a promise, the value you return will automatically be wrapped in a promise.

    async function increment(num) {
      return num + 1;
    }
    
    // Even though you returned a number, the value is
    // automatically wrapped in a promise, so we call
    // `then` on it to access the returned value.
    //
    // Logs: 4
    increment(3).then(num => console.log(num));
    

    Same thing even if there's no return! (Promise { undefined } is returned)

    async function increment(num) {}
    

    Same thing even if there's an await.

    function defer(callback) {
      return new Promise(function(resolve) {
        setTimeout(function() {
          resolve(callback());
        }, 1000);
      });
    }
    
    async function incrementTwice(num) {
      const numPlus1 = await defer(() => num + 1);
      return numPlus1 + 1;
    }
    
    // Logs: 5
    incrementTwice(3).then(num => console.log(num));
    

    Promises auto-unwrap, so if you do return a promise for a value from within an async function, you will receive a promise for the value (not a promise for a promise for the value).

    function defer(callback) {
      return new Promise(function(resolve) {
        setTimeout(function() {
          resolve(callback());
        }, 1000);
      });
    }
    
    async function increment(num) {
      // It doesn't matter whether you put an `await` here.
      return defer(() => num + 1);
    }
    
    // Logs: 4
    increment(3).then(num => console.log(num));
    

    In my synopsis the behavior is indeed inconsistent with traditional return statements. It appears that when you explicitly return a non-promise value from an async function, it will force wrap it in a promise. I don't have a big problem with it, but it does defy normal JS.

    ES6 has functions which don't return exactly the same value as the return. These functions are called generators.

    function* foo() {
      return 'test';
    }
    
    // Logs an object.
    console.log(foo());
    
    // Logs 'test'.
    console.log(foo().next().value);