TypeScript: arguments keyword in async functions

546
June 28, 2019, at 04:10 AM

Consider the following strange behavior I noticed in one of my projects:

async function hello() {
  return arguments;
}

When TypeScript's compilation target is set to es3 or es5, the above file fails to compile with the following error:

error TS2522: The 'arguments' object cannot be referenced in an async function or method in ES3 and ES5. Consider using a standard function or method.
2   return arguments;
           ~~~~~~~~~

However, with a higher compilation target (I've tested es2017 and esnext) there is no error.

What is it about the arguments keyword that prevents it from being used in async functions when TypeScript's compilation target is set to es3 or es5?

A few notes:

  • When replicated in modern JavaScript, this function does not throw an exception
  • This behavior can only be replicated in async functions

My Hypothesis

I suspect that because Promise needs to be polyfilled in es3 and es5, the polyfill cannot support arguments because it is dependant on the function callee.

Further reading: ES5.1 Spec ยง 10.6 Arguments Object

Answer 1

It's because Async functions get transpiled to a basic polyfilled implementation of generators; this implementation basically swaps out the body of your function to wrap it in another function, therefore any use of arguments will never access the original arguments of hello but instead of __generator

An example of this is below where hello which takes no arguments and generates the following in which the body of the function is wrapped in another function.

async function hello() {
    console.log(arguments);
} // becomes.....

function hello() {
    return __awaiter(this, arguments, void 0, function () {
        return __generator(this, function (_a) {
            console.log(arguments);
            return [2 /*return*/];
        });
    });
}

to re-iterate, console.log(arguments) has moved from the context of hello to the context of __generator meaning it would never behave how you expect it would. If you're targetting modern browsers (non-IE) you can set your compile target to ES6+ in which case this restriction will be removed.

Answer 2

Just use the spread operator

hello(...args: any[])

now you have an array or the arguments that were passed in.

Answer 3

You are absolutely right.

I'd like to share another point of view on why that does not work:

JavaScript has task based concurrency, which means that the code is broken into small "chunks" (tasks) and one of them gets executed at a time. If you have something asynchronous that gets divided into multiple tasks, one to start the async action and one to work with the results when the async action is done. Other tasks can run in the meantime, thus allowing for concurrency.

Now the smallest possible chunk of execution was (before async) a function: A function always runs to completion, you can't split up a function into multiple tasks.

With the introduction of the async keyword, there are async functions that don't run to completion. They will be split up into smaller tasks (through awaits).

Now if you have to transpile an async function into a regular function you have one problem: You can't split up the tasks. Therefore you need multiple functions to represent one async function:

   async function(arg) { await a(); await b(); }
   // becomes
   function(arg) { return a().then(function () { b(); }); }

Now as one can see that does not transpile exactly: While arg was the argument of the only async function, only the outer function has that arg. However that is usually not a problem, wether you access arg in the current scope or in an outer scope does not change the way things work (except you redeclare it).

However there is one thing that was changed, and which made up this answer: arguments. As we have two functions we do have two arguments objects. Now one could also mimic the arguments object, but then you would have to use other unsupported newer language features (getters / setters, Proxies). And to be honest: You should not use arguments and thus transpiling it is not worth the trouble.

READ ALSO
Optimum way of traversing a large dataset

Optimum way of traversing a large dataset

I have a large dataset of the values as JSONFollowing is a small snapshot of that JSON object

77
Uncaught ReferenceError: process is not defined when retrieving environment variables with dotenv-webpack

Uncaught ReferenceError: process is not defined when retrieving environment variables with dotenv-webpack

I built a React app from scratch (ie: not using create-react-app) and have been trying to retrieve environment variables from aenv file in my root directory

143
JavaScript private methods

JavaScript private methods

To make a JavaScript class with a public method I'd do something like:

168