DEV Community 👩‍💻👨‍💻

Luka Vidaković
Luka Vidaković

Posted on

Async without await

Some developers get annoyed by async functions that don't use await functionality. There are even linter rules that don't allow the usage of async functions if they don't contain the await keyword. I'm curious to know why is this a problem?

For example, I use async functions without the await functionality in some occasions:

  1. to indicate that execution will be asynchronous if that's not obvious

    async function run() {
      return doSomething()
    }
    
  2. to mock an async call or to simply prepare the code to work out of the box with asynchronous logic if transfer to asynchronous execution is anticipated. Looks cleaner than wrapping the return value with a Promise manually.

    async function fetchData() {
      return {foo: 'bar'}
    }
    

Linter rule that denies the usage of async function without the await forces you to change the beginning of your function definition every time you decide to ditch the await and return something immediately, change the inside logic to handle multiple call in parallel or similar. For every such change you are forced to go back and forth with your function definition and create unnecessary version control mess. That's what's really annoying, at least to me(#opinion).

Top comments (4)

Collapse
 
tiomno profile image
Israel Tiomno

Thanks for the article Luka, I agree with your examples. Actually, I have a rule on my IDE that warms me of unnecessary await in the return statement.

What's your opinion about this other snippet that I see utterly pointless? Why would you add the async keyword in such a case?

async function asyncCall() {
  return new Promise((resolve, reject) => {
    // Some fancy code here calling resolve() and reject()
  });
}
Collapse
 
apisurfer profile image
Luka Vidaković Author

Hey Israel! I believe that this one would go even more into the area of personal and team preferences. I tend to use async in places like services, and similar groups of code that tend to grow in complexity, to have a clear indication of how the code inside the function behaves. Another reason I do that is that I don't want to change the function "signature" if function becomes more complex and I decide to add the await mechanism later on. That way commits are a bit cleaner. Whereas in a case when you add the "async" keyword within a commit, you can't immediately tell if the function was already async until you look at the code inside it(code reviews).

Your example code makes it pretty obvious that it's an async function(even without the async keyword) I agree. But depending on the complexity of this particular part of the code, new Promise(... code block will sometimes move to a utility or service of some kind and it might no longer be obvious that this outer function is in fact async, depending on the naming of the used function/service. At least in a bit more complex parts of the code, so that's the use case I'd find this fairly legitimate to do. We could argue that with this approach you are basically preparing your code for future changes that might not happen.

My approach is to use async in places where I think the function will contain more than a few lines of code, will use external services, or in some often used functions where I want to clearly indicate that it's an async function. That usually leaves me with a cleaner git change history later on and saves me a bit of time by being more obvious than relying on the readability of the code inside the function or the function name.

Most of this boils down to personal preference. But I'd argue that having strict lint rules regarding an "async" keyword is a pain in the neck most of the time and doesn't really contribute to readability or the quality of code. Maybe I'm wrong.

Hopefully, I managed to make myself a bit clearer here 😅

Collapse
 
tiomno profile image
Israel Tiomno

Thanks for the answer, Luka! Fair enough! :)
This construction makes sense to me now after getting your point and will probably follow that approach in the future. 👍

Collapse
 
martinfromsweden profile image
Martin Czerwinski

well. removin or adding await in an async fuction can totally change the flow and error handling in caller in an unexpected way. Consider this simple code:


function fireAndForgetPromise() {
return new Promise((_, reject) => {
reject('error'); // this promise rejects/fails at some point
});
}

async function asyncFnc() {
// await fireAndForgetPromise();
fireAndForgetPromise(); // someone removed await

  console.log(`A promise that might reject is nested ...`);
Enter fullscreen mode Exit fullscreen mode

}

const ret = asyncFnc()
.then(data => {
console.log(.THEN => ${data});
})
.catch(err => {
console.log(.CATCH => ${err});
});

 console.log(`Returned => `, ret);
Enter fullscreen mode Exit fullscreen mode

Results in:

`A promise that might reject is nested ...
Returned => Promise { }
.THEN => undefined
Unhandled ERROR!!! Bla bla bla.. rejecting a promise which was not handled with .catch()

But is await is used we get:

Returned => Promise { }
.CATCH => error

So when not knowing what nested async function might do it is dangerous remove (or not use) await at all in async function just to mark it nests some async code...

This post blew up on DEV in 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!