Programming September 11

Why is the async keyword needed in JavaScript?

Last week a colleague asked me what the purpose of the async keyword was in JavaScript. Not because he didn't know how to use async/await. He was wondering why the async keyword was needed to use await. This set me off on a quest to find the answer. To avoid beating around the bush: its purpose is backwards compatibility.

Any experienced JavaScript developer is probably well versed in the usage of async/await these days. The await keyword unwraps a Promise. Until the Promise is either fulfilled or rejected the execution of the code after it is put on hold and the event loop is released to do other work. But why is the async keyword needed in order for us to use await in a function?

A common misconception seems to be that it "is used to communicate that the function is async" to anyone who uses it. However, that's not true. Consider the following code:

export async function fetchTextLength() {
const resp = await fetch("https://www.wikipedia.org");
const text = await resp.text();
return text.length;
}

The function above is marked async, but as a consumer of it that's hardly relevant. What is relevant is that it returns a Promise. Indeed, if we hover over it in a file that uses it in VS Code that's what we see:

Further, I think we can all agree that to a consumer of the above module it shouldn't matter if we decide to change the module to instead have the following implementation:

export function fetchTextLength() {
    return inner();
}

async function inner() {
    const resp = await fetch("https://www.wikipedia.org");
    const text = await resp.text();return text.length;
}

The important thing is that our function returns a Promise, not whether it's marked as async or not. So, if the purpose of the async keyword isn't to communicate anything to other developers then what's its purpose? One could think that it's being used by the JavaScript interpreter (or JIT compiler) to identify that the function is using await and to do its thing, implicitly having the function return a Promise. While that's true the interpreter could easily do that simply by looking at the code and identifying usage of the await keyword.

The real motivation for the async keyword can be found by digging through the original proposal for async/await in ECMAScript. There in a comment by Brian Terlson in issue 88 we can read the following:

"Long ago we considered whether it was possible to await everywhere but we could not find a good way to do it without breaking existing usage of await as an identifier"

In other words, with the introduction of await in ECMAScript a new reserved keyword needed to be added to the language. However, you can't just go around adding reserved keywords to programming languages if you want to be compatible with existing code. This is especially true for interpreted ones run in browsers. So the async keyword was introduced as a way to ensure backwards compatibility.

To illustrate, the below code is perfectly valid and outputs "test":

function test() {
    const await = "test";
    console.log(await);
}

test();

However, if we add the async keyword, which would not have been valid in older versions of JS, to the function like below we'll get an error when we try to run it:

async function test() {
    const await = "test";
    console.log(await);
}

test();

The above code produces:

const await = "test";
        ^^^^^

SyntaxError: Unexpected reserved word

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

More about Programming