DEV Community

Benjamin
Benjamin

Posted on • Edited on

Practical Beginners Guide to Problems with JS async/await

What is it

I don't want to go too in depth into what async/await is, and how it came about as there are many other good blog posts about it, but it is basically a syntax sugar around Promises, and gives your code the ability to be asynchronous

How to use it

The async/await syntax has given us the ability to have Promises in a more readable and cleaner way then the native Promise syntax. The basic usage of it, looks something like:

const GetSomeResource = async () => {
    return fetch('https://someurl.com');
}

const DoStuff = async () => {
    const result = await GetSomeResource();
    ...
}
Enter fullscreen mode Exit fullscreen mode

This has the obvious problem of not handling errors, but there are cases where maybe we don't want to.

Problems

There are a few gotchas with async/await, especially if your application lacks the linting around it. I have seen both Senior and Junior devs fall into the simple trap of forgetting an await somewhere, which can make debugging and testing quite unreliable and confusing. So what happens when someone forgets the simple await syntax? e.g.

const AddSomeResource = async () => {
    await fetch('https://someurl.com/user', { Method: 'POST', body: ... });
}

const GetSomeResource = async () => {
    return fetch('https://someurl.com');
}

const DoStuff = async () => {
    AddSomeResource();
    const result = await GetSomeResource();
    ...
}
Enter fullscreen mode Exit fullscreen mode

What would happen here is that the AddSomeResource function would run in the background while other tasks happened. So if in this example, anything relied on that function posting data, there's a chance that it wouldn't have been finished executing, and could cause intermittent errors in your application.
There is another case however where you don't rely on that resource elsewhere in the same scope, and everything seems to be working fine when it gets tested.

However once the external service is down and starts timing out, because the error hasn't been caught or passed up the chain to be dealt with elsewhere it starts logging errors while your other code is running. But the chain of functions associated with that call still seem to be working fine, until something gets called that requires that resource somewhere else in the application. This can send you down a bit of a rabbi hole and waste possibly several hours trying to debug what's actually going on

Forgetting await can be easier than it seems, especially when not using linting and some kind of typing framework such as Typescript. e.g.

const DoSomeStuffAndReturnBool = async () => {
    const getNum = await GetSomeNumAsync();
    if (getNum === 0)
        return true;
    return false;
}

const DoStuff = async () => {
    const isTrue = DoSomeStuffAndReturnBool();
    ...
}
Enter fullscreen mode Exit fullscreen mode

As you can see, especially to someone starting out in JS, it could be easy to not think about the async keyword and just see it returns a bool. But in the case of async, it always returns the result as a Promise

Thinking an await, awaits all the way down

I recently had a discussion with another developer who thought that the await keyword, would await all the child functions too, even the ones without await keywords. e.g.

const AddSomeResource = async () => {
    await fetch('https://someurl.com/user', { Method: 'POST', body: ... });
}

const DoStuff = async () => {
    AddSomeResource();
    ...
}

const DoParentStuff = async () => {
    // This does not automatically await the AddSomeResource call in DoStuff()
    await DoStuff();
}
Enter fullscreen mode Exit fullscreen mode

Conclusions

Add some linting around async and Promises so you don't forget to await, otherwise you could spend too much time debugging your application or getting inconsistent results

Top comments (0)