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;");
This would create a function equivalent to as if we had written this:
const add = function(a, b) { return a + b; }
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);
};
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
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;
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)));
6
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)