4 min read

Things I wish I knew about… JavaScript functions

Especially coming from a C/Python/Elixir background, there were some things about JavaScript functions that I really didn’t get to start with.
Things I wish I knew about… JavaScript functions

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

This one doesn’t:

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 cando something, doesn’t mean you should. For reference, see Kyle Simpson’s wonderful flowchart.

Flowchart for when to use brackets
Brackets 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.

If the braces are omitted, then the return is implied and must be omitted as well.

One statement, with implied return:

foo => foo + 1

More than one statement, with explicit return:

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!