async function implicitly returns promise?
Asked Answered
M

5

199

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.

Multiversity answered 9/2, 2016 at 21:22 Comment(10)
What does console.log show?Gintz
it's the value passed by the promise resolve function, not the promise itselfMultiversity
Perhaps await unwraps the result from promise.Rover
actually, I was wrong, it logs a promiseMultiversity
"To be more clear, there is a difference between the above and..." Why is there a difference between your two code samples? In both getVal functions you return a non-promise and you log a promise. They behave the same way.Manille
I was trying to get at the extra line "console.log(val); // logs 'yikes' or whatever"...we return val explicitly, but it gets converted into a promise implicitly; it was just a extra line of code to demonstrate the inconsistency I saw.Multiversity
JavaScript's promises are trying to mimic c#'s async await behavior. However, there was a lot of structure in place historically to support that with c#, and none in JavaScript. So while in many use cases it may seem to be very similar, it is somewhat of a misnomer.Mowery
you say it's not coherent, but if the method is asynchronous, how can it return a concrete value? it has to return a promise or it wouldn't be asynchronous.Altman
yep that is correct, just a little confusing, since it's implicit...aka even if there is no return statement it still returns a promise...seen?Multiversity
One thing the answers here seems to get wrong: an async function will not wrap your result in a Promise if necessary/if it ain't one. It will wrap it in a Promise period. If the value you return happens to be a Promise/PromiseLike/Thenable (has a then-function) then the Promise that the async function returns will resolve to whatever your Promise resolves. And if you write async function getVal(){ return await doSomethingAync() } you basically do function getVal(){ return Promise.resolve(Promise.resolve(doSomethingAync())) } one from the return, and one from the await.Centigram
M
240

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);
Manille answered 9/2, 2016 at 21:28 Comment(3)
"the value you return will automatically be wrapped in a promise" by the static method Promise.resolve, i.e, if the return statement of an async function is - return x; it implicitly becomes - return Promise.resolve(x);Hofstetter
Is is considered bad practise to just return the automatically created promise instead of explicitly creating it yourself? Somehow I like the clean approach in many cases.Blancablanch
No, I don't believe it is bad practice to rely on the automatically-created promise. I think an intended consequence of async function is to allow you to pipe to other promise-returning functions without ever having to have "Promise" appear in your code. e.g. function async myFunc() { const val1 = await otherAsyncFunc1(); const val2 = await otherAsyncFunc1(); return val1 + val 2; } function async main() { const result = await myFunc(): console.log("The result is " + result"); }Keese
L
39

Yes, an async function will always return a promise.

According to the tc39 spec, an async function desugars to a generator which yields Promises.

Specifically:

async function <name>?<argumentlist><body>

Desugars to:

function <name>?<argumentlist>{ return spawn(function*() <body>, this); }

Where spawn "is a call to the following algorithm":

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
        var gen = genF.call(self);
        function step(nextF) {
            var next;
            try {
                next = nextF();
            } catch(e) {
                // finished with failure, reject the promise
                reject(e);
                return;
            }
            if(next.done) {
                // finished with success, resolve the promise
                resolve(next.value);
                return;
            }
            // not finished, chain off the yielded promise and `step` again
            Promise.resolve(next.value).then(function(v) {
                step(function() { return gen.next(v); });
            }, function(e) {
                step(function() { return gen.throw(e); });
            });
        }
        step(function() { return gen.next(undefined); });
    });
}
Lucaslucca answered 9/2, 2016 at 21:47 Comment(2)
"The short version is that an async function desugars to a generator which yields Promises." I think you may be confusing async function with async function*. The former simply returns a promise. The latter returns a generator that yields promises.Overtrick
This answer is largely a reference to the spec and, after review, I don't believe there's any confusion. It's true, async functions return promises, but in order to do this they desugar to generators which yield promises.Lucaslucca
M
27

Your question is: If I create an async function should it return a promise or not? Answer: just do whatever you want and Javascript will fix it for you.

Suppose doSomethingAsync is a function that returns a promise. Then

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

is exactly the same as

async function getVal(){
    return doSomethingAsync();
}

You probably are thinking "WTF, how can these be the same?" and you are right. The async will magically wrap a value with a Promise if necessary.

Even stranger, the doSomethingAsync can be written to sometimes return a promise and sometimes NOT return a promise. Still both functions are exactly the same, because the await is also magic. It will unwrap a Promise if necessary but it will have no effect on things that are not Promises.

Meuse answered 3/11, 2020 at 18:26 Comment(0)
M
-4

Just add await before your function when you call it :

var ret = await  getVal();
console.log(ret);
Muttra answered 28/10, 2019 at 8:54 Comment(1)
await is only valid in async functionWineglass
W
-9

async doesn't return the promise, the await keyword awaits the resolution of the promise. async is an enhanced generator function and await works a bit like yield

I think the syntax (I am not 100% sure) is

async function* getVal() {...}

ES2016 generator functions work a bit like this. I have made a database handler based in top of tedious which you program like this

db.exec(function*(connection) {
  if (params.passwd1 === '') {
    let sql = 'UPDATE People SET UserName = @username WHERE ClinicianID = @clinicianid';
    let request = connection.request(sql);
    request.addParameter('username',db.TYPES.VarChar,params.username);
    request.addParameter('clinicianid',db.TYPES.Int,uid);
    yield connection.execSql();
  } else {
    if (!/^\S{4,}$/.test(params.passwd1)) {
      response.end(JSON.stringify(
        {status: false, passwd1: false,passwd2: true}
      ));
      return;
    }
    let request = connection.request('SetPassword');
    request.addParameter('userID',db.TYPES.Int,uid);
    request.addParameter('username',db.TYPES.NVarChar,params.username);
    request.addParameter('password',db.TYPES.VarChar,params.passwd1);
    yield connection.callProcedure();
  }
  response.end(JSON.stringify({status: true}));

}).catch(err => {
  logger('database',err.message);
  response.end(JSON.stringify({status: false,passwd1: false,passwd2: false}));
});

Notice how I just program it like normal synchronous particularly at

yield connection.execSql and at yield connection.callProcedure

The db.exec function is a fairly typical Promise based generator

exec(generator) {
  var self = this;
  var it;
  return new Promise((accept,reject) => {
    var myConnection;
    var onResult = lastPromiseResult => {
      var obj = it.next(lastPromiseResult);
      if (!obj.done) {
        obj.value.then(onResult,reject);
      } else {
       if (myConnection) {
          myConnection.release();
        }
        accept(obj.value);
      }
    };
    self._connection().then(connection => {
      myConnection = connection;
      it = generator(connection); //This passes it into the generator
      onResult();  //starts the generator
    }).catch(error => {
      reject(error);
    });
  });
}
Winer answered 9/2, 2016 at 21:34 Comment(3)
"async is an enhanced generator function" - no, it really is not.Intercept
As stated above - 'async functions' do indeed return a Promise. Conceptually at least, the main point of the 'async' statement is to wrap that function's return values in a promise. You can even 'await' on a plain old function that returns a Promise, and it all works, because 'async function' === 'function returning Promise'.Paltry
@bergi, actually, it is an enhanced generator function. a generator function which always returns a promise .. or something.Multiversity

© 2022 - 2024 — McMap. All rights reserved.