Learn JavaScript: Novice to Ninja

Chapter 11: Further Functions

We covered functions back in chapter 4, but we were only just beginning to scratch the surface. In JavaScript, functions arefirst-class objects, which means they can be passed around in the same way as every other value. They can have their own properties and methods, as well as accepting other functions as parameters and being returned by other functions. This makes them a very flexible tool to work with in JavaScript, and there are a variety of techniques and patterns that can be used to make code cleaner.

In this chapter, we’ll cover the following topics:

  • Function properties and methods
  • Immediately Invoked function expressions
  • Self-defining functions
  • Recursive functions
  • Callbacks
  • Promises
  • Async functions
  • Functions that return functions
  • Closures
  • Introduction to functional programming
  • Currying
  • Project ― improve some of the functions using techniques from this chapter

Function Properties and Methods

The fact that functions are first-class objects means they can have properties and methods themselves. For example, all functions have a length property that returns the number of parameters the function has.

Let’s use the square() function that we wrote in chapter 4 as an example:

function square(x) {    return x*x;}

If we query the length property, we can see that it accepts one parameter:

square.length<< 1

Call and Apply Methods

The call() method can be used to set the value of this inside a function to an object that is provided as the first argument.

In the following example, the sayHello() function refers to an unspecific object called this that has a property called name :

function sayHello(){    return `Hello, my name is ${ this.name }`;}

We can create some objects that have a name property, then use the call() method to invoke the sayHello() function, providing each object as an argument. This will then take the value of this in the function:

const clark = { name: 'Clark' };const bruce = { name: 'Bruce' };sayHello.call(clark);<< 'Hello, my name is Clarke'sayHello.call(bruce);<< 'Hello, my name is Bruce'

If the function that’s called requires any parameters, these need to be provided as arguments after the first argument, which is always the value of this . For example, let’s update the sayHello() function to give a more generalized greeting that’s provided as an argument:

function sayHello(greeting='Hello'){    return `${ greeting }, my name is ${ this.name }`;}sayHello.call(clark, 'How do you do');<< 'How do you do, my name is Clark'sayHello.call(bruce);<< 'Hello, my name is Bruce'

If a function doesn’t refer to an object as this in its body, it can still be called using the call() method, but you need provide null as its first argument. For example, we could call the square() function using the call() method, like so:

square.call(null, 4)<< 16

The apply() method works in the same way, except the arguments of the function are provided as an array, even if there is only one argument:

square.apply(null, [4])<< 16

This can be useful if the data you’re using as an argument is already in the form of an array, although it’s not really needed in ES6, as the spread operator can be used to split an array of values into separate parameters.

These are two powerful methods, as they allow generalized functions to be written that are not tied to specific objects by being methods of that object. This gives flexibility over how the functions can be used.

Custom Properties

There is nothing to stop you adding your own properties to functions in the same way that you can add properties to any object in JavaScript. For example, you could add a description property to a function that describes what it does:

square.description = 'Squares a number that is provided as an argument'<< 'Squares a number that is provided as an argument'

Memoization

A useful feature of this is that it provides result caching, ormemoization.

If a function takes some time to compute a return value, we can save the result in a cache property. Then if the same argument is used again later, we can return the value from the cache, rather than having to compute the result again. For example, say squaring a number was an expensive computational operation that took a long time. We could rewrite the square() function so it saved each result in a cache object that is a property of the function:

function square(x){    square.cache = square.cache || {};    if (!square.cache[x]) {        square.cache[x] = x*x;    }    return square.cache[x]}

If we try calling the function a few times, we can see that the cache object stores the results:

square(3);<< 9square(-11);<< 121square.cache;<< {"3": 9, "-11": 121}

Immediately Invoked Function Expressions

AnImmediately Invoked Function Expression– or IIFE – (pronounced ‘iffy’) is an anonymous function that, as the name suggests, is invoked as soon as it’s defined. This is easily achieved by placing parentheses at the end of the function definition (remember we use parentheses to invoke a function). The function also has to be made into an expression, which is done by placing the whole declaration inside parentheses, as in this example:

(function(){const temp = 'World';console.log(`Hello ${temp}`);})();<< 'Hello World'

IIFEs are a useful way of performing a task while keeping any variables wrapped up within the scope of the function. This means the global namespace is not polluted with lots of variable names.

Temporary Variables

There is no way to remove a variable from a scope once it’s been declared. If a variable is only required temporarily, it may cause confusion if it’s still available later in the code. Even worse, the name of the variable may clash with another piece of code (an external JavaScript library, for example) resulting in errors. Placing any code that uses the temporary variable inside an IIFE will ensure it’s only available while the IIFE is invoked, then it will disappear. The example that follows uses an IIFE to swap the value of two global variables, a and b . This process requires the use of a temporary variable, called temp , which only exists while the IIFE is invoked:

let a = 1;let b = 2;(()=>{    const temp = a;    a = b;    b = temp;})();a;<< 2b;<< 1console.log(temp);<< Error: "temp is not defined"

This shows the variable temp does not exist after the function has been invoked.

Note that this technique is not needed to swap the values of two variables in ES6, as destructuring can be used, as shown below:

let [a,b] = [1,2];[a,b] = [b,a];a;<< 2b;<< 1

Initialization Code

An IIFE can be used to set up any initialization code that there’ll be no need for again. Because the code is only run once, there’s no need to create any reusable, named functions, and all the variables will also be temporary. An IIFE will be invoked once, and can set up any variables, objects and event handlers when the page loads. The following example logs a welcome message to the console, then eliminates all the temporary variables used in putting the message together:

(function() {    const name = 'Peter Parker'; // This might be obtained from a cookie in reality    const days = ['Sunday','Monday','Tuesday','Wednesday','Thursday', 'Friday','Saturday'];    const date = new Date(),today = days[date.getDay()];    console.log(`Welcome back ${name}. Today is ${today}`);})();<< 'Welcome back Peter Parker. Today is Tuesday'

Note that much of this can be achieved in ES6 by simply placing the code inside a block. This is because variables have block scope when const or let are used, whereas in previous versions of JavaScript, only functions maintained the scope of variables. The example above would work just as well using the following code:

{    const name = 'Peter Parker'; // This might be obtained from a cookie in reality    const days = ['Sunday','Monday','Tuesday','Wednesday','Thursday', 'Friday','Saturday'];    const date = new Date(),today = days[date.getDay()];    console.log(`Welcome back ${name}. Today is ${today}`);}<< 'Welcome back Peter Parker. Today is Tuesday'

Safe Use of Strict Mode

In the last chapter we discussed using strict mode to avoid any sloppy coding practices. One of the problems with simply placing 'use strict' at the beginning of a file is that it will enforce strict mode on all the JavaScript in the file, and if you’re using other people’s code, there’s no guarantee that they’ve coded in strict mode.

To avoid this, the recommended way to use strict mode is to place all your code inside an IIFE, like so:

(function() {    'use strict';// All your code would go inside this function})();

This ensures that only your code inside the IIFE is forced to use strict mode.

Creating Self-contained Code Blocks

An IIFE can be used to enclose a block of code inside its own private scope so it doesn’t interfere with any other part of the program. Using IIFEs in this way means code can be added or removed separately. The example shows two blocks, A and B, that are able to run code independently of each other:

(function() {    // block A    const name = 'Block A';    console.log(`Hello from ${name}`);    }());    (function() {    // block B    const name = 'Block B';    console.log(`Hello from ${name}`);}());<<  Hello from Block A    Hello from Block B

Notice that both code blocks include a variable called name , but the modules don’t interfere with each other. This is a useful approach for separating parts of a program into discrete sections, especially for testing purposes.

Again, this can be achieved in ES6 by simply placing the different parts of code into blocks. ES6 also supports a much more powerful module pattern that is covered in Chapter 15.

Functions that Define and Rewrite Themselves

The dynamic nature of JavaScript means that a function is able to not only call itself, but define itself, and even redefine itself. This is done by assigning an anonymous function to a variable that hasthe same name as the function.

Consider the following function:

function party(){    console.log('Wow this is amazing!');    party = function(){        console.log('Been there, got the T-Shirt');    }}

This logs a message in the console, then redefines itself to log a different message in the console. When the function has been called once, it will be as if it was defined like this:

function party() {    console.log('Been there, got the T-Shirt');}

Every time the function is called after the first time, it will log the message ‘Been there, got the T-Shirt’:

party();<< 'Wow this is amazing!'party();<< 'Been there, got the T-Shirt'party();<< 'Been there, got the T-Shirt'

If the function is also assigned to another variable, this variable will maintain the original function definition and not be rewritten. This is because the original function is assigned to a variable, then within the function, a variable with the same name as the function is assigned to a different function. You can see an example of this if we create a variable called beachParty that is assigned to the party() functionbeforeit is called for the first time and redefined:

function party(){    console.log('Wow this is amazing!');    party = function(){        console.log('Been there, got the T-Shirt');    }}const beachParty = party; // note that the party function has not been invokedbeachParty(); // the party() function has now been redefined, even though it hasn't been called explicitly<< 'Wow this is amazing!'party(); << 'Been there, got the T-Shirt'beachParty(); // but this function hasn't been redefined<< 'Wow this is amazing!'beachParty(); // no matter how many times this is called it will remain the same<< 'Wow this is amazing!'

Losing Properties

If any properties have previously been set on the function, these will be lost when the function redefines itself. In the previous example, we can set a music property, and see that it no longer exists after the function has been invoked and redefined:

function party() {    console.log('Wow this is amazing!');    party = function(){    console.log('Been there, got the T-Shirt');    }}party.music = 'Classical Jazz'; // set a property of the functionparty();<< "Wow this is amazing!"party.music; // function has now been redefined, so the property doesn't exist<< undefined

This is called theLazy Definition Patternand is often used when some initialization code is required the first time it’s invoked. This means the initialization can be done the first time it’s called, then the function can be redefined to what you want it to be for every subsequent invocation.

Init-Time Branching

This technique can be used with the feature detection that we discussed in the last chapter to create functions that rewrite themselves, known as init-time branching. This enables the functions to work more effectively in the browser, and avoid checking for features every time they’re invoked.

Let’s take the example of our fictional unicorn object that’s yet to have full support in all browsers. In the last chapter, we looked at how we can use feature detection to check if this is supported. Now we can go one step further: we can define a function based on whether certain methods are supported. This means we only need to check for support the first time the function is called:

function ride(){    if (window.unicorn) {         ride = function(){        // some code that uses the brand new and sparkly unicorn methods        return 'Riding on a unicorn is the best!';        }    } else {        ride = function(){        // some code that uses the older pony methods        return 'Riding on a pony is still pretty good';        }    }    return ride();}

After we’ve checked whether the window.unicorn object exists (by checking to see if it’s truthy), we’ve rewritten the ride() function according to the outcome. Right at the end of the function, we call it again so that the rewritten function is now invoked, and the relevant value returned. One thing to be aware of is that the function is invoked twice the first time, although it becomes more efficient each subsequent time it’s invoked. Let’s take a look at how it works:

ride(); // the function rewrites itself, then calls itself<< 'Riding on a pony is still pretty good'

Once the function has been invoked, it’s rewritten based on the browser’s capabilities. We can check this by inspecting the function without invoking it:

ride<< function ride() {    return 'Riding on a pony is still pretty good';    }

This can be a useful pattern to initialize functions the first time they’re called, optimizing them for the browser being used.

Recursive Functions

A recursive function is one that invokes itself until a certain condition is met. It’s a useful tool to use when iterative processes are involved. A common example is a function that calculates thefactorialof a number:

function factorial(n) {    if (n === 0) {        return 1;    } else {        return n * factorial(n - 1);    }}

This function will return 1 if 0 is provided as an argument (0 factorial is 1), otherwise it will multiply the argument by the result of invoking itself with an argument of one less. The function will continue to invoke itself until finally the argument is 0 and 1 is returned. This will result in a multiplication of 1, 2, 3 and all the numbers up to the original argument.

Another example from the world of mathematics is theCollatz Conjecture. This is a problem that is simple to state, but, so far, has not been solved. It involves taking any positive integer and following these rules:

  • If the number is even, divide it by two
  • If the number is odd, multiply it by three and add one

For example, if we start with the number 18, we would have the following sequence:

18, 9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, …

As you can see, the sequence becomes stuck in a loop at the end, cycling through “4,2,1”. The Collatz Conjecture states that every positive integer will create a sequence that finishes in this loop. This has been verified for all numbers up to 5 × 2⁶⁰, but there is no proof it will continue to be true for all the integers higher than this. To test the conjecture, we can write a function that uses recursion to keep invoking the function until it reaches a value of 1 (because we want our function to avoid being stuck in a recursive loop at the end!):

function collatz(n, sequence=[n]) {    if (n === 1){        return `Sequence took ${sequence.length} steps. It was ${sequence}`;    }    if (n%2 === 0) {        n = n/2;    } else {         n = 3*n + 1;    }    return collatz(n,[...sequence,n]);}

This function takes a number as a parameter, as well as another parameter called sequence , which has a default value of an array containing the first parameter. The second parameter is only used when the function calls itself recursively.

The first thing the function does is tests to see if n has a value of 1. If it does, the function returns a message to say how many steps it took. If it hasn’t reached 1, it checks if the value of n is even (in which case it divides it by 2), or odd, in which case it multiplies by 3 and then adds 1. The function then calls itself, providing the new value of n and the new sequence as arguments. The new sequence is constructed by placing the old sequence and the value of n inside a new array and applying the spread operator to the old sequence.

Let’s see what happens to the number 18:

collatz(18);<< 'Sequence took 21 steps. It was 18,9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1'

As you can see, it takes 21 steps, but eventually it ends up at 1.

Have a go at using the function and see if you can find a value above 5 × 2⁶⁰ that doesn’t end at 1 — you’ll be famous if you do!

You can read more about recursive functions inthis article on SitePoint.

Callbacks

We covered callbacks in Chapter 4. You’ll recall that they’re functions passed to other functions as arguments and then invoked inside the function they are passed to.

Event-driven Asynchronous Programming

Callbacks can be used to facilitate event-driven asynchronous programming. JavaScript is a single-threaded environment, which means only one piece of code will ever be processed at a time. This may seem like a limitation, but non-blocking techniques can be used to ensure that the program continues to run. Instead of waiting for an event to occur, a callback can be created that’s invoked when the event happens. This means that the code is able to run out of order, orasynchronously. Events can be DOM events, such as the click and keyPress that we looked at in Chapter 7, but they can also be events such as the completion of a file download, data returned from a database, or the result of a complex operation. By using callbacks, we ensure that waiting for these tasks to complete doesn’t hold up the execution of other parts of the program. Once the task has been completed, the callback will be invoked before returning to the rest of the program.

Here’s an example of a function called wait() that accepts a callback. To simulate an operation that takes some time to happen, we can use the setTimeout() function to call the callback after a given number of seconds:

function wait(message, callback, seconds){    setTimeout(callback,seconds * 1000);    console.log(message);}

Now let’s create a callback function to use:

function selfDestruct(){    console.log('BOOOOM!');}

If we invoke the wait() function then log a message to the console, we can see how JavaScript works asynchronously:

wait('This tape will self-destruct in five seconds ... ', selfDestruct, 5);console.log('Hmmm, should I accept this mission or not ... ?');<< 'This tape will self-destruct in five seconds ... '<< 'Hmmm, should I accept this mission or not ... ? '<< 'BOOOOM!'

When the wait() function is invoked, any code inside it is run, so the message ‘This tape will self destruct in five seconds … ‘ is displayed. The setTimeout() function is asynchronous, which means that the callback provided as an argument is placed on top of a stack that gets cleared once the rest of the program has run. This means that control is handed back to the program and the next line in the program is run, which displays the message ‘Hmmm, should I accept this mission or not … ?’ Then, after five seconds, the callback is retrieved from the stack and invoked. This demonstrates that the setTimeout() function did not block the rest of the program from running. This is known as the JavaScriptevent-loop, and you can learn more about it by watchingthis legendary video.

Remember, though, that JavaScript is still single-threaded, so only one task can happen at once. If an event only takes a small amount of time to happen, it will still have to wait until other parts of the program have executed before the callback is invoked. For example, let’s see what happens if we set the waiting time to be zero seconds:

wait('This tape will self-destruct immediately ... ', selfDestruct, 0);console.log('Hmmm, should I accept this mission or not ... ?');<< 'This tape will self-destruct immediately ... '<< 'Hmmm, should I accept this mission or not ... ?'<< 'BOOOOM!'

Notice the callback in the wait() function is still invoked last, despite the wait time being set to zero seconds. We would have expected the callback to have been invoked immediately, but a callback always has to wait for the current execution stack to complete before it’s invoked. In this case, the current execution stack is the rest of the function and code already entered in the console. Once these have executed, the callback is invoked before handing control back to the main program.

Callback Hell

The increase in the use of asynchronous programming in JavaScript has meant that more and more callbacks are being used. This can result in messy and confusing ‘spaghetti code’. This is when more than one callback is used in the same function, resulting in a large number of nested blocks that are difficult to comprehend.

Callback hell is the term used to refer to this tangled mess of code, and it’s such a common issue that it evenhas its own website!

To illustrate this, let’s say we had written a game that required the following tasks to be completed:

  • The user logs in and a user object is returned
  • The user ID is then used to fetch player information from the server
  • The game then loads based on the player information

All these operations are asynchronous, so can be written as functions that use callbacks invoked once each task has been completed.

The code might look like the snippet shown below:

login(userName, function(error,user) {    if(error){        throw error;    } else {          getPlayerInfo(user.id, function(error,info){        if(error){        throw error;        } else {            loadGame(info, function(error,game) {                if(error){                        throw error;                    } else {                    // code to run game                }            });        }        });    }});

You may have noticed there isn’t much actual code in the example above. The example only shows the flow from one function to the other, and yet it still manages to look extremely complicated due to the large number of nested if-else statements. In reality, there would be lots more code to implement the actual functionality of the login()getPlayerInfo() and loadGame() functions.

Error-first Callbacks

The code example above uses theerror-firstcallback style popularized by Node.js. In this coding pattern, callbacks have two arguments. The first is the error argument, which is an error object provided if something goes wrong when completing the operation. The second argument is any data returned by the operation that can be used in the body of the callback.

Promises

Apromiserepresents the future result of an asynchronous operation. Promises don’t do anything that can’t already be achieved using callbacks, but they help simplify the process, and avoid the convoluted code that can result from using multiple callbacks.

The Promise Life Cycle

When a promise is created, it calls an asynchronous operation and is then said to bepending. It remains in this state while the operation is taking place. At this stage, the promise is said to beunsettled. Once the operation has completed, the promise is said to have beensettled. A settled promise can result in two different outcomes:

  • Resolved ― the asynchronous operation was completed successfully.
  • Rejected ― the asynchronous operation didn’t work as expected, wasn’t successfully completed or resulted in an error.

Both these outcomes will return any relevant data, and you can take the appropriate action based on the outcome of the promise.

A Super Promise

Imagine if a shady character gave you a red pill, and promised that if you took it, you’d be a superhero. Being an adventurous sort, you swallow the pill and wait to see what happens. You’re currently in the pending phase of a promise, waiting to see what the result will be.

Suddenly you find that you have the power to dodge bullets as if time was standing still! The promise has been resolved, and now you need to go off and use your newly acquired powers.

But if nothing happens, you would reject the promise and warn people that a stranger is wandering around giving out red pills and peddling a fanciful story.

This scenario puts a comic-book spin on the phases of a promise. There is a pending phase while you wait on the results of an operation (taking the pill). Then once the promise is settled, you deal with the results in an appropriate way ― by using your superpowers if the promise is resolved, or dealing with any problems if it doesn’t work out.

The Promise of a Burger Partyis a brilliant post by Mariko Kosaka that explains the concept of promises by comparing them to ordering a burger!

Creating A Promise

A promise is created using a constructor function. This takes a function called anexecutoras an argument. The executor initializes the promise and starts the asynchronous operation. It also accepts two functions as arguments: the resolve() function is called if the operation is successful, and the reject() function is called if the operation fails. The general layout of a promise can be seen in the code below:

const promise = new Promise( (resolve, reject) => {    // initialization code goes here    if (success) {        resolve(value);    } else {        reject(error);    }});

A Dicey Example

Let’s take a look at an example of a promise that uses the dice object we created back in chapter 5:

const dice = {    sides: 6,    roll() {        return Math.floor(this.sides * Math.random()) + 1;    }}

Now let’s create a promise that uses the dice.roll() method as the asynchronous operation and considers rolling a 1 as a failure, and any other number as a success:

const promise = new Promise( (resolve,reject) => {    const n = dice.roll();    setTimeout(() => {        (n > 1) ? resolve(n) : reject(n);    }, n*1000);});

This creates a variable called promise that holds a reference to the promise. The promise calls the roll() method and stores the return value in a variable called n . Next, we use an if-else block to specify the conditions for success (rolling any number higher than 1) and failure (rolling a 1). The setTimeout() method we met in Chapter 9 is used to add a short delay based on the number rolled. This is to mimic the time taken for an asynchronous operation to complete.

Notice that both the resolve() and reject() functions return the value of the n variable. This can be used when dealing with the outcome of the promise once it’s been settled.

Dealing With A Settled Promise

Once a promise has been settled, the then() method can be used to deal with the outcome. This method accepts two arguments. The first is afulfilment functionthat’s called when the promise is resolved. Any data returned from the resolve() function will be passed along to this function. The second argument is a rejection function that’s called if the promise is rejected. Similar to the fulfilment function, the rejection function receives any data returned from the reject() function.

In the case of our dice example, both functions will receive the value of the number rolled. Let’s have a look at how we could deal with that:

promise.then( result => console.log(`Yes! I rolled a ${result}`), result => console.log(`Drat! ... I rolled a ${result}`) );

The first argument is simply a function that logs a celebratory message to the console, stating the number rolled (this is passed to the then() method as the variable result ). The second argument logs an annoyed message and, again, states the number rolled.

Alternatively, the catch() method can be used to specify what to do if the operation fails instead:

promise.catch( result => console.log(`Drat! ... I rolled a ${result}`));

The then() and catch() methods can be chained together to form a succinct description of how to deal with the outcome of the promise:

promise.then( result => console.log(`I rolled a ${result}`) )            .catch( result => console.log(`Drat! ... I rolled a ${result}`) );

To try this code out, paste the following code into your browser console or useJS Bin with ES6/Babel enabled:

const dice = {sides: 6,    roll() {        return Math.floor(this.sides * Math.random()) + 1;    }}console.log('Before the roll');const roll = new Promise( (resolve,reject) => {    const n = dice.roll();    if(n > 1){        setTimeout(()=>{resolve(n)},n*200);    } else {        setTimeout(()=>reject(n),n*200);    }});roll.then(result => console.log(`I rolled a ${result}`) ).catch(result => console.log(`Drat! ... I rolled a ${result}`) );console.log('After the roll');

When you press the ‘Run’ button, you should see the following output in the console:

before promise promise pending... after promise

Then there should be a pause, while the promise is resolved, followed by the resulting message:

Drat! ... I rolled a 1

The messages in the console also give an insight into the asynchronous nature of JavaScript. Notice that the last message ‘after promise’ is displayedbeforethe result of the settled promise. This shows that the language will continue to process the rest of the code while the promise is being resolved, before coming back and dealing with the result of the promise.

Chaining Multiple Promises

Promises come into their own when multiple asynchronous tasks are required to be carried out one after the other. If each function that performs an asynchronous operation returns a promise, we can chain the then() methods together to form a sequential piece of code that’s easy to read. Each promise will only begin once the previous promise has been settled.

For example, the player logging in to a game that produced the callback hell earlier, could be written in a much nicer way by using promises:

login(userName).then(user => getPlayerInfo(user.id)).then(info => loadGame(info)).catch( throw error)

A number of new additions to the language return promises, and you’ll see more examples of them used later in the book.

Async Functions

Async functions were added to the ES2017 specification. These functions are preceded by the async keyword and allow you to write asynchronous code as if it was synchronous. This is achieved by using the await operator before an asynchronous function. This will wrap the return value of the function in a promise that can then be assigned to a variable. The next line of code is not executed until the promise is resolved.

The example below shows how the loadGame() function can be written an async function:

async function loadGame(userName) {    try {        const user = await login(userName);        const info = await getPlayerInfo (user.id);        // load the game using the returned info    }    catch (error){        throw error;    }}

In the example, the loadGame function is preceded by the async keyword, meaning the function will run in an asynchronous fashion. We then wrap each step of the process in a try block, so any errors are caught. Inside this block, we can write each step in the order it’s meant to be processed, so we start by assigning the variable user to the return value of the login() function. The await operator will ensure the next line of code is not executed until the login() function returns a user object. The getPlayerInfo() function is also preceded by the await operator. Once this function returns a result, it’s assigned to the variable info , and this can then be used to load the actual game. A catch block is used to deal with any errors that may occur.

Generalized Functions

Callbacks can be used to build more generalized functions. Instead of having lots of specific functions, one function can be written that accepts a callback. For example, let’s create a function that returns a random integer between two values that are provided as arguments, a and b, or if only 1 argument is provided, it will return a random integer between 1 and the argument provided:

function random(a,b=1) {    // if only 1 argument is provided, we need to swap the values of a and b    if (b === 1) {        [a,b] = [b,a];    }     return Math.floor((b-a+1) * Math.random()) + a;}random(6);<< 4random(10,20);<< 13

This is an example of an abstraction, as it wraps all the logic cleanly away inside the function.

We could refactor this function to make it more generic by adding a callback parameter, so a calculation is performed on the random number before it’s returned:

function random(a,b,callback) {    if (b === undefined) b = a, a = 1; // if only one argument is supplied, assume the lower limit is 1        const result = Math.floor((b-a+1) * Math.random()) + a    if(callback) {        result = callback(result);    }    return result;}

Now we have a function where more flexibility can be added using a callback. For example, we can use the square() function from earlier in the chapter to produce a random square number from one to 100:

function square(n) {    return n*n;}random(1,10,square);<< 49

Or a random even number from two to ten:

random(1,5, (n) => 2 * n );<< 8

Notice that in the last example, the callback is an anonymous function that is defined inline as one of the random() function’s arguments.

Functions That Return Functions

We’ve just seen that functions can accept another function as an argument (a callback), but they can also return a function.

The example below shows a function called returnHello() that returns a ‘Hello World’ function:

function returnHello() {    console.log('returnHello() called');    return function() {        console.log('Hello World!');    }}

When the returnHello() function is invoked, it logs a message to the console then returns another function:

returnHello()<< returnHello() called

To make use of the function that is returned, we need to assign it to a variable:

const hello = returnHello();<< returnHello() called

Now we can invoke the ‘Hello World’ function by placing parentheses after the variable that it was assigned to:

hello()<< Hello World!

This might seem a bit pointless, but let’s now take it a step further and use this technique to create a generic ‘greeter’ function that takes a particular greeting as a parameter, then returns a more specific greeting function:

function greeter(greeting = 'Hello') {    return function() {        console.log(greeting);    }}const englishGreeter = greeter();englishGreeter();<< Helloconst frenchGreeter = greeter('Bonjour');frenchGreeter();<< Bonjourconst germanGreeter = greeter('Guten Tag');germanGreeter();<< Guten Tag

Closures

Closuresare one of JavaScript’s most powerful features, but they can be difficult to get your head around initially.

Function Scope

Back in Chapter 2, we saw the value of a variable was only available inside the block it was created inside if the const or let keywords were used. This also applies to the body of a function if the var keyword is used.

In the following example, there are two variables: outside , which is available throughout the program, and inside , which is only available inside the function:

const outside = 'In the global scope';function fn() {    const inside = 'In the function scope';}outside<< 'In the global scope'inside<< ReferenceError: inside is not defined

It appears we’re unable to access the variable inside outside the scope the function.

This is because the variable inside is only kept ‘alive’ while the function is active. Once the function has been invoked, any references to variables inside its scope are removed.

It turns out, however, that we can gain access to variables outside the function where it was created, and after the function has been invoked.

A closure is a reference to a variable that was created inside the scope of another function, but is then kept alive and used in another part of the program.

One of the key principles in creating closures is that an ‘inner’ function, which is declared inside another function, has full access to all of the variables declared inside the scope of the function in which it’s declared (the ‘outer’ function). This can be seen in the example below:

function outer() {    const outside = 'Outside!';    function inner() {        const inside = 'Inside!';        console.log(outside);        console.log(inside);    }    console.log(outside);    inner();}

The outer() function only has access to the variable outside , which was declared in its scope. The inner() function, however, has access to the variable inside , declared in its scope, but also the variable outside , declared outside its scope, but from within the outer() function.

We can see this when we invoke the outside() function:

outer()<< Outside!Inside!Outside!

This means that whenever a function is defined inside another function, the inner function will have access to any variables that are declared in the outer function’s scope.

Returning Functions

As we saw in the example above, functions declared from within another function have access to any variables declared in the outer function’s scope.

Aclosureis formed when the inner function is returned by the outer function, maintaining access to any variables declared inside the enclosing function.

function outer() {    const outside = 'Outside!';    function inner() {        const inside = 'Inside!';        console.log(outside);        console.log(inside);    }    return inner;}

We can now assign a variable to the return value of the outer() function:

const closure = outer();

The variable closure now points to the inner() function that is returned by the outer() function.

What makes this a closure is that it now has access to the variables created insideboththe outer() and inner() functions, as we can see when we invoke it:

closure();<< Outside!Inside!

This is important as the variable outside should only exist while the outer() function is running. The closure maintains access to this variable, however, even though the outer() has been invoked.

A closure doesn’t just have access to the value of a variable, it can also change the value of the variable long after the function in which it was originally declared has been invoked.

A Practical Example

A closure is formed when a function returns another function that then maintains access to any variables created in the original function’s scope. In the following example, two variables, a and b , are created in the scope of the closure() function. This then returns an anonymous arrow function that maintains access to the variables a and b even after the closure() function has been invoked:

function closure() {    const a = 1.8;    const b = 32;    return c => c * a + b;}

Now we can create a new function by invoking the closure() function and assigning the return value to a variable called toFahrenheit :

const toFahrenheit = closure();

This new function can then be invoked with its own argument, but the values of a and b from the original function are still kept ‘alive’:

toFahrenheit(30);<< 86

A Counter Example

Closures not only haveaccessto variables declared in a parent function’s scope, they can also change the value of these variables. This allows us to do things like create a counter() function like the one in the example below:

function counter(start){    let i = start;    return function() {        return i++;    }}

This function starts a count using the variable i . It then returns a function that uses a closure that traps and maintains access to the value of i . This function also has the ability to change the value of i , so it increments i by one every time it’s invoked. The reference to the variable i that is defined in the original function is maintained in the new function via a closure.

We can create a counter by assigning the return value of the counter() function to a variable:

const count = counter(1);

The variable count now points to a function that has full access to the variable i that was created in the scope of the counter() function. Every time we invoke the count() function, it will return the value of i and then increment it by 1:

count();<< 1count();<< 2

Generators

ES6 introduced support for generators. These are special functions used to produce iterators that maintain the state of a value.

To define a generator function, an asterisk symbol ( * ) is placed after the function declaration, like so:

function* exampleGenerator() {// code for the generator goes here}

Calling a generator function doesn’t actually run any of the code in the function; it returns a Generator object that can be used to create an iterator that implements a next() method that returns a value every time the next() method is called.

For example, we can create a generator to produce a Fibonacci-style number series (a sequence that starts with two numbers and the next number is obtained by adding the two previous numbers together), using the following code:

function* fibonacci(a,b) {    let [ prev,current ] = [ a,b ];    while(true) {        [prev, current] = [current, prev + current];        yield current;    }}

The code starts by initializing the first two values of the sequence, which are provided as arguments to the function. A while loop is then used, which will continue indefinitely due to the fact that it uses true as its condition, which will obviously always be true. Every time the iterator’s next() method is called, the code inside the loop is run, and the next value is calculated by adding the previous two values together.

Generator functions employ the special yield keyword that is used to return a value. The difference between the yield and the return keywords is that by using yield , the state of the value returned is remembered the next time yield is called. Hence, the current value in the Fibonacci sequence will be stored for use later. The execution of the loop is paused after every yield statement, until the next() method is called again.

To create a generator object based on this function, we simply assign a variable to the function, and provide it with two starting numbers as arguments:

const sequence = fibonacci(1,1);

The generator object is now stored in the sequence variable. It inherits a method called next() , which is then used to obtain the next value produced by the yield command:

sequence.next();<< 2sequence.next();<< 3sequence.next();<< 5

It’s also possible to iterate over the generator to invoke it multiple times:

for (n of sequence) {    // stop the sequence after it reaches 100    if (n > 10) break;    console.log(n);}<< 8<< 13<< 21<< 34<< 55<< 89

Note that the sequence continued from the last value produced using the next() method. This is because a generator will maintain its state throughout the life of a program.

Functional Programming

Functional programming has gained momentum in recent years, with a dedicated following. The popularity of purely functional languages, such as Clojure, Scala and Erlang, sparked an interest in functional programming techniques that continues to grow. JavaScript has always supported functional-style programming due to functions being first-class objects. The ability to pass functions as arguments, return them from other functions, and use anonymous functions and closures, are all fundamental elements of functional programming that JavaScript excels at.

Functional programming is a programming paradigm. Other examples of programming paradigms include object oriented programming and procedural programming. JavaScript is a multi-paradigm language, meaning that it can be used to program in a variety of paradigms (and sometimes a mash-up of them!). This flexibility is an attractive feature of the language, but it also makes it harder to adopt a particular coding style as the principles are not enforced by the language. A language such as Haskell, which is a purely functional language, is much stricter about adhering to the principles of functional programming.

Pure Functions

A key aspect of functional programming is its use of pure functions. A pure function is a function that adheres to the following rules:

1) The return value of a pure function should only depend on the values provided as arguments. It doesn’t rely on values from somewhere else in the program.

2) There are no side-effects. A pure function doesn’t change any values or data elsewhere in the program. It only makes non-destructive data transformations and returns new values, rather than altering any of the underlying data.

3) Referential transparency. Given the same arguments, a pure function will always return the same result.

In order to follow these rules, any pure function must have:

  • At least one argument; otherwise the return value must depend on something other than the arguments of the function, breaking the first rule
  • A return value; otherwise there’s no point in the function (unless it has changed something else in the program – in which case, it’s broken the ‘no side-effects’ rule).

Pure functions help to make functional programming code more concise and predictable than in other programming styles. Referential transparency makes pure functions easy to test as they can be relied on to return the same values when the same arguments are provided. Another benefit is that any return values can be cached, since they’re always the same (see the section on Memoization above). The absence of any side-effects tends to reduce the amounts of bugs that can creep into your code, because there are no surprise dependencies as they only rely on any values provided as arguments.

The following example shows a pure function that writes the string provided as an argument backwards:

function reverse(string) {    return string.split('').reverse().join('');}

The function does not change the actual value of the argument, it just returns another string that happens to be the argument written backwards:

const message = 'Hello JavaScript';reverse(message);<< 'tpircSavaJ olleH'message // hasn't changed<< 'Hello JavaScript'

This is an example of a non-destructive data transformation, as the value stored in the variable, message , remains the same after it’s been passed through the function as an argument.

One point to note is that using const to declare variables will help to avoid destructive data transformations. This is because any variables that are assigned to primitive values using const cannot be changed (although variables that are assigned to non-primitive objects using const can still be mutated, so it’s not a complete solution).

Let’s take a look at hownotto write a pure function. The next example shows an impure function that returns the value of adding two values together:

let number = 42;let result = 0;function impureAdd(x) {    result = number + x;}impureAdd(10);result;<< 52

The function impureAdd() is an impure function, as it breaks the rules outlined above. It requires the value, number , which is defined outside of the function, it has the side effect of changing the value of result, and it would return a different value if the value of the variable number was different.

Here’s an example of a pure function that achieves the same result:

const number = 42;function pureAdd(x,y) {    return x + y;}result = pureAdd(number,10);<< 52

This function requires the two arguments that it’s adding together, so the variable number has to be passed to it as an argument. There are no side-effects to this function, it simply returns the result of adding the two numbers together. This return value is then assigned to the variable, result , instead of the function updating the value of the variable. This function will also always return the same value given the same inputs.

Functional programming uses pure functions as the building blocks of a program. The functions perform a series of operations without changing the state of any data. Each function forms an abstraction that should perform a single task, while encapsulating the details of its implementation inside the body of the function. This means that a program becomes a sequence of expressions based on the return values of pure functions. The emphasis is placed on usingfunction compositionto combine pure functions together to complete more complex tasks.

You can read more about function composition inthis article on Sitepoint.

By only performing a single task, pure functions are more flexible, as they can be used as the building blocks for many different situations, rather than be tightly coupled with one particular operation. They also help to make your code more modular, as each function can be improved upon or replaced without interfering with any of the other functions. This makes it easy to replace one function with another to either improve the behavior, modify it slightly, or even change it completely.

As an example, we can use the square() function that we created in Chapter 4:

function square(x){    return x*x;}

This function can then be used to create a hypotenuse() function that returns the length of the hypotenuse of a right-angled triangle, (The hypotenuse is the longest side of a right-angled triangle. Its length can be found using the formula a² + b²= c², which is commonly known as Pythagoras’ Theorem.) given the lengths of the other two sides as parameters:

function hypotenuse(a,b) {    return Math.sqrt(square(a) + square(b));}hypotenuse(3,4);<< 5

The hypotenuse() function uses the square() function to square the numbers, rather than hard coding a*a and b*b into the function. This means that if we find a more optimal way to square a number, we only have to improve the implementation of the square() function. Or if we find an alternative way of calculating the hypotenuse that doesn’t rely on squaring numbers (however unlikely that is!), we could just swap the square() function for another.

To illustrate the point further, we can create another function called sum() that takes an array as an argument as well as a callback. The callback is used to transform the value of each item in the array using the map() method. Then the reduce() method is used to find the sum of all items in the array:

function sum(array, callback) {    if(callback) {        array = array.map(callback);    }    return array.reduce((a,b) => a + b );}

The callback makes the function more flexible as it allows a transformation to be performed on all the numbers in the array before finding the sum. This means it can be used to find the sum of an array of numbers:

sum([1,2,3]); // returns 1 + 2 + 3<< 6

Alternatively, we can find the sum after the numbers have been squared by adding the square() function as a callback:

sum([1,2,3], square); // returns 1^2 + 2^2 + 3^2<< 14

The sum() function can also be used to create a mean() function that calculates the mean of an array of numbers:

function mean(array) {    return sum(array)/array.length;}mean([1,2,3];<< 2

We can now use the sum()square() and mean() functions as the building blocks to build a variance() function that calculates the variance (The variance is a measure of spread that measures deviation from the mean.) of an array of numbers:

function variance(array) {    return sum(array,square)/array.length - square(mean(array))}    variance([1,2,3])    << 0.666666666666667

By separating each piece of functionality into individual functions, we’re able to compose a more complex function. These functions can also be used to create more functions that require the mean, sum or variance.

Higher-Order Functions

Higher-order functions are functions that accept another function as an argument, or return another function as a result, or both.

Closures are used extensively in higher-order functions as they allow us to create a generic function that can be used to then return more specific functions based on its arguments. This is done by creating a closure around a function’s arguments that keeps them ‘alive’ in a return function. For example, consider the following multiplier() function:

function multiplier(x){    return function(y){        return x*y;    }}

The multiplier() function returns another function that traps the argument x in a closure. This is then available to be used by the returned function.

We can now use this generic multiplier() function to create more specific functions, as can be seen in the example below:

doubler = multiplier(2);

This creates a new function called doubler() , which multiplies a parameter by the argument that was provided to the multiplier() function (which was 2 in this case). The end result is a doubler() function that multiplies its argument by two:

doubler(10);<< 20

The multiplier() function is an example of a higher-order function. This means we can use it to build other, more specific functions by using different arguments. For example, an argument of 3 can be used to create a tripler() function that multiplies its arguments by 3:

tripler = multiplier(3);tripler(10);<< 30

This is one of the core tenets of functional programming: it allows generic higher-order functions to be used to return more specific functions based on particular parameters.

Here’s another example, where we create a higher-order power() function. It returns a second function that calculates values to the power of a given argument. To make this calculation, the second function uses a closure to maintain a reference to the initial argument supplied to the power() function:

function power(x) {    return function(power) {        return Math.pow(x,power);    }}

Now we can create some more specific functions that use this higher-order, generic function to build them. For example, we could implement a twoExp() function that returns powers of 2 , like so:

twoExp = power(2);<< function (power) {    return Math.pow(x,power);}twoExp(5);<< 32

We can also create another function called tenExp() that returns powers of 10 :

tenExp = power(10);<< function (power) {    return Math.pow(x,power);}tenExp(6);<< 1000000

When a higher-order function returns another function, we can use a neat trick to create an anonymous return function and immediately invoke it with a value instead by using double parentheses. The following example will calculate 3 to the power 5:

power(3)(5);<< 243

This works because power(3) returns a function, to which we immediately pass an argument of 5 by adding it in parentheses at the end.

Currying

Curryingis a process that involves the partial application of functions. It’s named after the logicianHaskell Curry— not the spicy food — just like the programming language Haskell is. His work on a paper by Moses Schönfinkel lead to the development of this programming technique.

A function is said to be curried when not all arguments have been supplied to the function, so it returns another function that retains the arguments already provided, and expects the remaining arguments that were omitted when the original function was called. A final result is only returned once all the expected arguments have eventually been provided.

Currying relies on higher-order functions that are able to return partially applied functions. All curried functions are higher-order functions because they return a function, but not all higher-order functions are curried.

The power() function above is an example of a higher-order function that can be curried as it will expects two arguments, but will return another, curried function, if the only one argument is provided.

Currying allows you to turn a single function into a series of functions instead. This is useful if you find that you’re frequently calling a function with the same argument. For example, the following multiplier() function is a generic function that returns the product of two numbers that are provided as arguments:

function multiplier(x,y) {    return x * y;}

A basic use of this function could be to calculate a tax rate of 22% on a £400 sale using 0.22 and 400 as arguments:

const tax = multiplier(0.22,400);<< 88

We could make this function more useful by adding some code at the start that allows it to be curried so it returns another function if only one argument is provided:

function multiplier(x,y) {    if (y === undefined) {        return function(z) {        return x * z;        }    } else {        return x * y;    }}

Now, if you found yourself frequently calculating the tax using the same rate of 22%, you could create a new curried function by providing just 0.22 as an argument:

calcTax = multiplier(0.22);<< function (z){    return x * z;}

This new function can then be used to calculate the tax, without requiring 0.22 as an argument:

calcTax(400);<< 88

By currying the more generic multiplier() function, we’ve created a new, more specific function, calcTax() , that is simpler to use.

A General Curry Function

In the last example, we hard-coded the multiplier() function so it could be curried. It’s possible to use a curry() function to take any function and allow it to be partially applied. The curry function is the following:

function curry(func,...oldArgs) {    return function(...newArgs) {        const allArgs = [...oldArgs,...newArgs];        return func(...allArgs);    }}

This function accepts a function as its first argument, which is stored as func . The rest operator is used to collect all the other arguments together as ...oldArgs . These are the arguments of the function that is the first argument. It then returns a function that accepts some new arguments that are stored in the variable ...newArgs . These are then lumped together with ...oldArgs to make ...newArgs using the spread operator. The return value of this function is obtained by invoking the original function, which is accessed using a closure over func and passed the combined arguments ...allArgs .

Now let’s create a generic divider() function that returns the result of dividing its two arguments:

const divider = (x,y) => x/y;

If we test this out, we can see that it does indeed return the quotient (The quotient of two numbers is the result obtained by dividing one number by another.) of its two arguments:

divider(10,5);<< 2

We can now use our curry() function to create a more specific function that finds the reciprocal (The reciprocal of a number is the result obtained by dividing one by the number. If you multiply a number by its reciprocal, the answer is always one.) of numbers:

const reciprocal = curry(divider,1);

This creates a new function called reciprocal() that is basically the divider() function, with the first argument set as 1. If we test it out, we can see that it does indeed find the reciprocal of the argument provided:

reciprocal(2);<< 0.5

This example shows how currying uses generic functions as the building blocks for creating more specific functions.

Getting Functional

Advocates of functional programming can be quite partisan about its benefits. But even adopting some of its principles, such as keeping functions as pure as possible, and keeping changes in state to a minimum, will help improve the standard of your programming.

There’s a lot more you can learn about functional programming. Find out more by having a look at thenumerous articles published on SitePointor by signing up for this excellent course onSitePoint Premium.

Quiz Ninja Project

Our quiz is shaping up nicely, but it’s getting a little boring always answering the questions in the same order. Let’s use the random() function that we created in this chapter to shake things up a bit. We can use it to make the questions appear at random, rather than just asking them in the order in which they appear in the array. We’ll do this by mixing up the array of questions that we select the question from. Because the pop() method always removes the last element in an array, this will mean the question selected will always be selected at random.

Our first task is to add the random() function near the top of main.js :

function random(a,b=1) {    // if only 1 argument is provided, we need to swap the values of a and b    if (b === 1) {        [a,b] = [b,a];    }    return Math.floor((b-a+1) * Math.random()) + a;}

Now we need to create a shuffle() function. This will take an array and change the position of each element. Add the following function declaration underneath the random() function:

function shuffle(array) {    for (let i = array.length; i; i--) {        let j = random(i)-1;        [array[i - 1], array[j]] = [array[j], array[i - 1]];    }}

This function uses a for loop and iteratesbackwardsthrough the array, selecting a random element to swap each element with. This ensures that the array gets completely shuffled.

Now we have our functions, we can use them to select a question at random. All we need to do is update the game.ask() method with an extra line that invokes the shuffle() function on the game.questions array before we use the pop() method to select a question. This can be achieved by updating the game.ask() function to the following:

ask(name){    console.log('ask() invoked');    if(this.questions.length > 0) {    shuffle(this.questions);    this.question = this.questions.pop();    const question = `What is ${this.question.name}'s real name?`;    view.render(view.question,question);}

Have a go at playing the game by opening index.html in a browser. The random() and shuffle() functions have made it a bit more interesting to play now that the question appears in a random order:

Random questions on Quiz Ninja

You can see a live example onCodePen.

Chapter Summary

  • Functions have built-in properties such as length , but can have custom properties added.
  • All functions have call() and apply() methods that can invoke a function with the value of this bound to an object that is provided as an argument.
  • Immediately Invoked Function Expressions or IIFEs are functions that are enclosed in parentheses and immediately followed by double parentheses so they’re invoked. They are useful for namespacing variables and setting default values.
  • Functions are able to dynamically redefine themselves in the body of the function, depending on certain conditions.
  • A recursive function will keep invoking itself until a certain condition is met.
  • A callback is a function that’s provided as an argument to another function.
  • Callbacks are frequently used in asynchronous programming as part of the event loop. This means that a program can continue to run in a single thread while waiting for another task to be completed.
  • Promises can be used instead of callbacks to deal with multiple asynchronous actions in sequence. They also provide a nicer mechanism for handling errors.
  • Functions that return other functions are known as higher-order functions.
  • A closure is the process of keeping a reference to a variable available outside the scope of the function it was originally defined in.
  • A generator is created by placing an asterisk character (*) after the function keyword.
  • A generator function will return an iterator object that provides a next() method, which returns the next value in a sequence that is defined in the generator function.
  • Functional programming involves breaking processes down into steps that can be applied as a series of functions.
  • Pure functions are functions that don’t rely on the state of the code they are called from, have no side-effects, and always give the same result when given the same arguments (referential transparency).
  • Currying or partial application is the process of applying one argument at a time to a function. A new function is returned until all the arguments have been used.

In the next chapter, we’ll be looking at the principles of object-oriented programming in JavaScript.

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16