3

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

4
  • 3
    Your hypothesis sounds right. Remove return arguments than have a look at what the transpiler generates Commented Jun 27, 2019 at 20:22
  • This has less to do with promises than rather with the generator continuations. I'm pretty sure you'll see the same error in a generator function. And to be honest, they should've been able to work around this, just like you can "work around" having no arguments or this in an arrow function. Commented Jun 27, 2019 at 21:17
  • @bergi but whey should they? There is no use of arguments. Commented Jun 27, 2019 at 21:21
  • @JonasWilms Just to follow the spec closely (which allows arguments) and not have users trip over error messages :-) Commented Jun 27, 2019 at 21:32

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

Comments

4

Just use the spread operator

hello(...args: any[])

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

1 Comment

That's what I ended up using yes, I just wanted to see some insight into why arguments fails in async functions
0

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.

4 Comments

Generators did enjoy engine support before async functions :)
@ry- right, but I don't think they are worth mentioning here, because they're ES6 too.
Async functions aren’t ES6, though. (… not that I think it makes the answer “not useful”…)
@ry- the "too" was indeed missleading, my point is: The OP tries to transpile to ES5 thus generators from ES6 don't help ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.