DEV Community

Jeremy
Jeremy

Posted on

Async and Generator Function Constructors in JavaScript

Sometimes I like to browse through the ECMAScript/JavaScript specification or the HTML specification at random, to see if I come across anything interesting or unexpected. That's how I first became aware of the "structured cloning" algorithm (back when it was still only for internal use), and today I found a new piece of JavaScript trivia that really tickled my interest.

The Background

In JavaScript, if you want to define a new function dynamically/at runtime, from source code, you can do so by using the Function constructor. For example, if we want to define a function that adds two numbers, we could do so like this:

const add = new Function("a", "b", "return a + b;");
Enter fullscreen mode Exit fullscreen mode

This would create a function equivalent to as if we had written this:

const add = function(a, b) { return a + b; }
Enter fullscreen mode Exit fullscreen mode

In practice, this is essentially never a good idea. It's basically the same as using eval, with an unreasonable risk of introducing security issues or negatively affecting the performance of your program, as well as preventing effective static analysis by tools like TypeScript. It has been widely discouraged for a very long time, and is entirely blocked by Content Security Policy configurations that are enabled by default by many modern web frameworks. But let's set these concerns aside for a moment.

Later versions of ECMAScript added three new types of functions, which are declared using slightly different syntaxes. Here are contrived examples of each:

// Async Functions:
const add = async function(a, b) {
  return (await a) + (await b);
};

// Generator Functions:
const add = function*(a, b) {
  yield a + b; 
}

// Async Generator Functions:
const add = async function*(a, b) {
  yield (await a) + (await b);
};
Enter fullscreen mode Exit fullscreen mode

You can see that each different type of function requires a different syntax where the function keyword appears. However, the Function constructor only lets us specify the contents of the argument list and body; it doesn't provide any way to affect the function keyword, so it can't be used to create these new types of functions. And if we try in the JavaScript console, we don't see any AsyncFunction, GeneratorFunction, or AsyncGeneratorFunction constructors we can use instead: none of those identifiers are declared in the global namespace.

Uncaught ReferenceError: AsyncFunction is not defined
Enter fullscreen mode Exit fullscreen mode

This isn't really surprising. These types of functions were added to the language long after use of the Function constructor was already widely discouraged. Although the existing constructor could never be removed from the language due to backwards-compatibility concerns, it's understandable that they wouldn't want to introduce new ones to allow these patterns to be continued in new code being written in the modern versions of the language.

The Surprise

But much to my surprise, the ECMAScript specification does define behavior for each of these constructors, even though they're not directly exposed under any name. We can find these in the 2024 edition of the specification under section 27.7.1 for "The AsyncFunction Constructor", section 27.3.1 for "The GeneratorFunction Constructor", and section 27.4.1 for "The AsyncGeneratorFunction Constructor.

However, as mentioned, these aren't directly exposed under any name; the corresponding identifiers are not declared in the global namespace. So are they just an irrelevant internal detail of the spec?

Nope! For better or worse, we can actually use them. Although they're not exposed under any name, we can get references to these constructors through the .constructor property of existing functions of each type, with code like this:

const AsyncFunction = (async function() {}).constructor;
const GeneratorFunction = (function*() {}).constructor;
const AsyncGeneratorFunction = (async function*() {}).constructor;
Enter fullscreen mode Exit fullscreen mode

With these definitions available, we can dynamically create functions of these new types just like we can create normal functions:

const add = new AsyncFunction("a", "b",
  "return (await a) + (await b);"
);

console.log(await add(Promise.resolve(4), Promise.resolve(2)));
Enter fullscreen mode Exit fullscreen mode
6
Enter fullscreen mode Exit fullscreen mode

Should you ever do this? Almost certainly not. This type of code is discouraged for good reasons. Frontend code should be using a Content Security Policy that forbids eval-style code like this from even running. I'm mostly just sharing this because I thought it was a fun piece of trivia, and I was surprised that it was included in the specification, even though it does make a certain kind of sense for consistency.

But if you really do find yourself in a situation where you actually need this capability (maybe you're writing some kind of custom JavaScript REPL), now you know that it exists!

Top comments (0)