Things I wish I knew about… JavaScript functions
Paul Walker
Posted on May 29, 2021
Especially coming from a C/Python/Elixir background, there were some things about JavaScript functions that I really didn’t get to start with. I thought I’d write them down in the hope they’ll help someone else on their journey.
I should note this is probably part one - there’s bound to be more things I learn about JavaScript functions as I keep using the language.
When one is async, all are async
I didn’t really understand how JavaScript does async when I started using it, so I spent quite some time trying to work out how a function could get a result from an asynchronous call and return it without the function caller having to be async itself.
If you’re aiming for the same thing, I’ll save you the bother - you can’t do it. I initially had high hopes for something like the construction below.
async function iAmAsync(num) {
return num * 2;
}
function iUseThen(num) {
return iAmAsync(num).then(res => res + 1);
}
console.log("iUseThen(3) =>", iUseThen(3));
What I didn’t realise was that iAmAsync(3).then(...)
will implicitly return a Promise, meaning the whole of iUseThen
will return a Promise.
iUseThen(3) => Promise { <pending> }
One approach I did find for using async functions in short scripts is to declare an anonymous async function and immediately invoke it:
(async function() {
const result = await somethingNetwork();
console.log("Result", result);
}) ()
What’s the difference between function
and =>
?
In JavaScript, =>
is called a ‘fat arrow’. Fat arrows are a shorthand way to create functions (with some restrictions as below):
function anonymous(name) {
console.log("Hello", name);
}
you can use:
name => console.log("Hello", name);
Apart from anything it saves coming up with lots of different names for anonymous functions.
Limitations of =>
As handy as this is, there are some limitations of the fat arrow form.
No this
A function defined with =>
doesn’t have a this
to reference. A (somewhat contrived) example - this works:
withFunction = {
answer: 42,
ask: function () {
console.log("The answer is:", this.answer);
}
};
withFunction.ask();
Producing:
The answer is: 42
withArrow = {
answer: 42,
ask: () => {
console.log("The answer is:", this.answer)
}
}
withArrow.ask();
The answer is: undefined
A more real-world example of this can be seen with Vuex - if you’re defining a mutation or an action and you use a fat arrow function, it probably won’t work as you expect.
As an implication of this — because there’s no this
, you can’t use super
either.
Can’t be used as constructors.
If you’re defining a class, you must use the full function foo(bar) {}
form.
Can’t use yield
I have to admit this hasn’t been a problem for me, I haven’t yet had a cause to use generators.
When to use (foo) =>
and when to use foo =>
?
The foo => ...
form accepts one and only one parameter, and even then only if it’s a simple form.
If you need to indicate no parameters, bracket are required.
() => console.log("I'm not listening to you");
If you need to pass two, (foo, bar) => ...
then it needs brackets. So this is fine:
foo => console.log("I'm a valid construction");
And this:
(foo, bar) => console.log("You gave me", foo, "and", bar);
But this is not:
foo, bar => console.log("While I won't run!");
Important note
If you need to do anything at all with that single parameter, you need brackets. For example - need to add a TypeScript type? Brackets. Need to destructure? Brackets. Want to supply a default parameter? Brackets. And so on.
What this boils down to - just because you can_do something, doesn’t mean you _should. For reference, see Kyle Simpson’s wonderful flowchart.
When to use foo => {bar; return baz}
and when foo => bar
?
If the function body is a single statement, you can omit the braces. Otherwise, the braces are required.
One statement:
foo => foo + 1
More than one statement:
foo => {
console.log("You gave me", foo);
return foo + 1;
}
Closures
Kind of a hybrid - part data, part function. I’ve come across closures before, but JavaScript makes them easier to use than the other languages I’ve spent much time with.
A closure is essentially a function together with the environment present when it was created.
function makeClosure(outerArgument) {
// Return a function which adds 'outerArgument' to
// whatever argument it's given.
return function(innerArgument) {
return outerArgument + innerArgument;
}
}
addOne = makeClosure(1)
console.log("Created addOne", addOne);
console.log("Two plus one is", addOne(2));
Which outputs:
$ node closure.js
Created addOne [Function (anonymous)]
Two plus one is 3
Note that the function returned is anonymous, simply because we didn’t name It in makeClosure
. It’s quite possible to name it, though I haven’t found it to serve much purpose (so far).
That’s a trivia example of a closure - for a more useful one see my other blog post on using Axios.
Conclusion
I hope that’s been a useful introduction for someone — wish I’d known these when I started with JavaScript!
Posted on May 29, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.