Creative Coding I: Design & Communication

Prof. Dr. Lena Gieseke | l.gieseke@filmuniversitaet.de | Film University Babelsberg KONRAD WOLF


Script 04 - JavaScript


Introduction

JavaScript, often abbreviated as JS, is an interpreted programming language (meaning it runs as is and you donā€™t need to compile it to execute your code), which allows you to implement complex things on web pages. Every time a web page does more than just sit there and display static information for you to look at, e.g., displaying timely content updates, interactive maps, animated 2D/3D graphics, scrolling video jukeboxes, etc., you can bet that JavaScript is probably involved. Alongside HTML and CSS, JavaScript is one of the three core technologies of the World Wide Web (we will come back to this). The vast majority of websites use JS, and all major web browsers have a dedicated JavaScript engine to execute it.

As a multi-paradigm language, JavaScript supports event-driven, functional, object-oriented and prototype-based programming styles (donā€™t worry about this if you donā€™t understand). Although there are strong outward similarities between JavaScript and Java, including language name, syntax, and respective standard libraries, they are two distinct languages are and differ greatly in design.

Please note that in class we only use modern JavaScript based on ECMAScript 2015.

Resources

Tutorials and References

I mainly use two resources for Javascript:

  • The Modern JavaScript Tutorial
    • In detail, easy to follow explanations šŸ˜
    • In my scripts I often just copy&paste from this tutorial (with the reference given, of course)
  • MDN JavaScript Reference
    • As reference
    • There are also tutorials and explanations on specific example scenarios
    • A bit hard to navigate

An interesting read is also Eloquent JavaScript. But it is lengthy and meant to be read as a whole.

Strict Mode

"use strict";

This is a directive for using modern JavaScript. It has to be at the top of the script. Always use it as ā€œmodernā€ mode changes the behavior of some built-in features and not using strict mode might lead to unexpected behavior.

All examples in class assume strict mode, unless (very rarely) specified otherwise. In p5 we donā€™t care about this for now.

Semicolons

A semicolon should be present after each statement, even if it could possibly be skipped.

semi

There are languages where a semicolon is truly optional and it is rarely used. In JavaScript, though, there are cases where a line break is not interpreted as a semicolon, leaving the code vulnerable to errors.

If youā€™re an experienced JavaScript programmer, you may choose a no-semicolon code style like StandardJS. Otherwise, itā€™s best to use semicolons to avoid possible pitfalls. The majority of developers put semicolons.

On a side note: Once in while I might forget to put a semicolon. That doesnā€™t mean that you should too.

Resources

Variables

Dynamic Typing

JavaScript is a loosely typed or a dynamic language. Variables in JavaScript are not directly associated with any particular value type, and any variable can be assigned (and re-assigned) values of all types:

let foo = 42;    // foo is now a number
foo     = 'bar'; // foo is now a string
foo     = true;  // foo is now a boolean

Variable Definition

There are three ways to declare a variable:

  1. let
  2. const
  3. var

let and const behave exactly the same way, except that const variables cannot be reassigned.

But var is a very different beast, that originates from old times. Itā€™s generally not used in modern scripts, but it actually just has a different functionality. For now just remember to use let.

There are two main differences of var:

  1. Variables have no block scope. They are either function-wide or global and are visible through blocks.
  2. Variable declarations are always processed at function start.

[The old ā€œvarā€] [Using variable declarations to improve readability]

Data Types

There are 7 basic types in JavaScript.

  • number for numbers of any kind: integer or floating-point
  • string for strings. A string may have one or more characters, thereā€™s no separate single-character type
  • boolean for true/false
  • null for unknown values ā€“ a standalone type that has a single value null
  • undefined for unassigned values ā€“ a standalone type that has a single value undefined
  • object for more complex data structures
    • All other types are called primitive because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities.
  • symbol for unique identifiers

The typeof operator allows us to see which type is stored in a variable.

  • Two forms: typeof x or typeof(x)
  • Returns a string with the name of the type, like ā€œstringā€.
  • For null returns ā€œobjectā€ ā€“ this is an error in the language, itā€™s not actually an object.
console.log(typeof 42);
// expected output: "number"

console.log(typeof 'blubber');
// expected output: "string"

console.log(typeof true);
// expected output: "boolean"

console.log(typeof declaredButUndefinedVariable);
// expected output: "undefined";

Equality Check

A regular equality check == has a problem. It cannot differentiate 0 from false:

console.log( 0 == false ); // true

The same thing happens with an empty string:

console.log( '' == false ); // true

This happens because operands of different types are converted to numbers by the equality operator ==. An empty string, just like false, becomes a zero.

What to do if weā€™d like to differentiate 0 from false?

A strict equality operator === checks the equality without type conversion.

In other words, if a and b are of different types, then a === b immediately returns false without an attempt to convert them.

console.log( 0 === false ); // false, because the types are different

There is also a ā€œstrict non-equalityā€ operator !== analogous to !=.

The strict equality operator is a bit longer to write, but makes it obvious whatā€™s going on and leaves less room for errors.

The Modern Javascript Tutorial: Data Types

Resources

Data Structures

In JavaScript mainly objects and arrays (which are a specific kind of object) provide ways to group several values into a single value.

Objects

Objects are associative arrays with several special features. Most objects in JavaScript have properties, the exceptions being null and undefined.

Definition

Properties are stored as key:value pairs, where:

  • Property keys must be strings or symbols (usually strings).
  • Values can be of any type.
let object_name = {
   key1: value1,
   key2: value2
}
let user = {        // an object
  name: "Sully",    // the key "name" stores the value "Sully"
  age: 30           // the key "age" stores the value 30
};

This is the same as:

let user = { name: 'Sully', age: 30 };

keyvalue
[programiz]

Accessing Properties

To access a property, we can use:

obj.property

  • The dot notation
    user.name;

obj['property']

  • Square brackets notation
    user['name'];

  • Square brackets allow to take the key from a variable

    let currentKey= 'name';
    user[currentKey];

Additional operators

  • To delete a property: delete obj.prop
  • To check if a property with the given key exists: 'key' in obj
  • To iterate over an object: for(let key in obj) loop
let user = { name: "Sully", age: 30 };

console.log('name' in user); //true

for(let key in user)
{
    // keys
    console.log( key );  
    // console output: name, age

    // values for the keys
    console.log( user[key] );
    // console output: Sully, 30
}

If you assign a value to a key that doesnā€™t exist in the object, the property will be automatically added:

let user = {        // an object
  name: "Sully",    // by key "name" store value "Sully"
  age: 30           // by key "age" store value 30
};

user.hobby = 'singing';

// now user is { name: 'Sully', age: 30, hobby: 'singing' }

You can test for the existence of a key with:

"key" in object // true if property "key" exists in object

Nested Objects

let user = { 
    name: 'John', 
    age: 20,
    // nested object
    preferences: {
        color: 'dark',
        tabs: 5
    }
}

// accessing property of user object
console.log(user.preferences); // {color: 'dark', tabs: 5}

// accessing property of the preferences object
console.log(user.preferences.tabs); // 5

Values By Reference

Objects are assigned and copied by reference. In other words, a variable stores not the object value, but a reference (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object. All operations via copied references (like adding/removing properties) are performed on the same single object.

let user = { name: 'Sully' };
let admin = user;
admin.name = 'Pete'; // changed by the "admin" reference

console.log(user.name);
// console output: 'Pete' -> changes are seen from the "user" reference

To make a ā€œreal copyā€ (a clone) we can use for example

Object.assign(dest[, src1, src2, src3...])

  • Arguments dest, and src1, ā€¦, srcN (can be as many as needed) are objects.
  • It copies the properties of all objects src1, ..., srcN into dest. In other words, properties of all arguments starting from the 2nd are copied into the 1st. Then it returns dest.
let user = { name: "Sully" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// copies all properties from permissions1 and permissions2 into user
Object.assign(user, permissions1, permissions2);
// now user = { name: "Sully", canView: true, canEdit: true }

If the receiving object (user) already has the same named property, it will be overwritten:

let user = { name: "Sully" };

// overwrite name, add isAdmin
Object.assign(user, { name: "Pete", isAdmin: true });

// now user = { name: "Pete", isAdmin: true }

To copy all properties of user into the empty object and returning it

let user = {
  name: "Sully",
  age: 30
};

let clone = Object.assign({}, user);

Be aware of objects as property values

let user = {
  name: "Sully",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

To fix that, we should use the cloning loop that examines each value of user[key] and, if itā€™s an object, then replicate its structure as well. That is called a deep cloning.

Thereā€™s a standard algorithm for deep cloning that handles the case above and more complex cases, called the Structured cloning algorithm. In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library lodash, the method is called _.cloneDeep(obj).

Objects in JavaScript are very powerful. Here weā€™ve just scratched the surface of a topic that is really huge.

The Modern Javascript Tutorial: Objects

Methods

(We will come back to thisā€¦)

The value of a key:value pair can be a function:

let cat = {
    name: 'Ernie',
    age: 5,
    // using function as a value
    makeSound: function() { console.log('meow') }
};

cat.makeSound(); // meow

this

To access a property of an object from within a method of the same object, you need to use the this keyword. this accesses itself.

let cat = {
    name: 'Ernie',
    age: 5,
    // using function as a value
    makeSound: function() { console.log('meow') },
    getName: function() { console.log('My name is', this.name) }
};

cat.getName();

[programiz]

Classes

If you want to work with several objects of the same type (meaning having the same key:value pairs), you can make as many copies of one object as you want (keep the copy by reference problematic in mind though).

Or you could create a template for an object and then derive object instances of that template.

The basic syntax is:

class MyClass {
    // class methods
    constructor() { ... }
    method1() { ... }
    method2() { ... }
    method3() { ... }
  ...
}
class Cat
{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    makeSound() { console.log('meow') }
    getName() { console.log('My name is', this.name) }
}

let ernie = new Cat('Ernie', 5);
console.log(ernie); // -> Cat { name: 'Ernie', age: 5 }
ernie.makeSound();  // -> meow
  • new Cat() creates a new object of the class template.
  • The constructor() method is called automatically by new, so we can initialize the object there.
  • Hence, when new Cat('Ernie', age) is called:
    • A new object is created.
    • The constructor runs with the given argument and assigns this.name to it.
  • With the created object we can then call class methods, such as ernie.makeSound().

Careful

  • It is in the constructor that you create class variables by using this.variablename=value
  • There is no function for class methods.
  • There are no commas between class methods.

You can also already declare and initialize the class variables at the top of your class (these are then called public instance fields), which makes the class a bit more readable:

class Cat
{
    name = 'catname';
    age = 0;

    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    makeSound() { console.log('meow') }
    getName() { console.log('My name is', this.name) }
}

let ernie = new Cat('Ernie', 5);
console.log(ernie.name); // -> Ernie

Careful

  • There is no let keyword for creating the class variables
  • To access these variable anywhere else inside of the class, you still must use ā€˜this.ā€™

[javascript]

Arrays

For an ordered collection, where we have a 1st, a 2nd, a 3rd element and so on, arrays are used. An array can store elements of any type.

let emptyArr = [];
let fruits = ["Apple", "Orange", "Plum"];

// mix of values
let arr = [ 'Apple', { name: 'John' }, true, function() { console.log('hello'); } ];

// get the object at index 1 and then show its name
console.log( arr[1].name ); // John

// get the function at index 3 and run it
arr[3](); // hello

fruits.pop(); // removes the last element, hence  "Plum"

// The call fruits.push(...) is equal to fruits[fruits.length] = ....
fruits.push('strawberry'); // appends the element to the end of the array

One of the oldest ways to cycle array items is the for loop over indexes:

let fruits = ["Apple", "Orange", "Pear"];

for (let i = 0; i < fruits.length; i++) 
{
    console.log( fruits[i] );
}

But for arrays there modern form of loop, for..of:

let fruits = ["Apple", "Orange", "Plum"];

// iterates over array elements
for (let fruit of fruits) 
{
    console.log( fruit );
}

Question:

let fruits = ["Apples", "Pear", "Orange"];

// push a new value into the "copy"
let shoppingCart = fruits;
shoppingCart.push("Banana"); 

// what's in fruits?
console.log( fruits.length ); //?

Internals

An array is a special kind of object. The square brackets used to access a property arr[0] actually come from the object syntax. Numbers are used as keys.

They extend objects providing special methods to work with ordered collections of data and also the length property. But at the core itā€™s still an object.

Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.

For instance, it is copied by reference:

let fruits = ["Banana"]

let arr = fruits; // copy by reference (two variables reference the same array)

console.log( arr === fruits ); // true

arr.push("Pear"); // modify the array by reference

console.log( fruits ); // Banana, Pear - 2 items now

But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, and there are other optimizations as well, to make arrays work really fast.

But they all break if we quit working with an array as with an ā€œordered collectionā€ and start working with it as if it were a regular object.

For instance, technically we can do this:

let fruits = []; // make an array

fruits[99999] = 5; // assign a property with the index far greater than its length

fruits.age = 25; // create a property with an arbitrary name

Thatā€™s possible, because arrays are objects at their base. We can add any properties to them.

But the engine will see that weā€™re working with the array as with a regular object. Array-specific optimizations are not suited for such cases and will be turned off, their benefits disappear.

The ways to misuse an array:

  • Add a non-numeric property like arr.test = 5
  • Make holes, like: add arr[0] and then arr[1000] (and nothing between them).
  • Fill the array in the reverse order, like arr[1000], arr[999] and so on.

Please think of arrays as special structures to work with the ordered data. They provide special methods for that. Arrays are carefully tuned inside JavaScript engines to work with contiguous ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object {}.

The Modern Javascript Tutorial: Arrays

JSON

JSON stands for JavaScript Object Notation and is widely used as a data storage and communication format on the Web, even in languages other than JavaScript. JSON is a data format that has its own independent standard and libraries.

JSON looks similar to JavaScriptā€™s way of writing arrays and objects, with a few restrictions. All property names have to be surrounded by double quotes, and only simple data expressions are allowe:no function calls, bindings, or anything that involves actual computation. Comments are not allowed in JSON.

A journal entry might look like this when represented as JSON data:

{
  "squirrel": false,
  "events": ["running", "climbing", "digging", "being cute"]
}


let json = `{
  name: "John",                     // mistake: property name without quotes
  "surname": 'Smith',               // mistake: single quotes in value (must be double)
  'isAdmin': false                  // mistake: single quotes in key (must be double)
  "birthday": new Date(2000, 2, 3), // mistake: no "new" is allowed, only bare values
  "friends": [0,1,2,3]              // here all fine
}`;

JavaScript gives us the functions JSON.stringify and JSON.parse to convert data to and from this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes. If an object has toJSON, then it is called by JSON.stringify.

let string = JSON.stringify({squirrel: false,
                             events: ["weekend"]});
console.log(string);
// console output:  {"squirrel":false,"events":["weekend"]}
console.log(JSON.parse(string).events);
// console output:  ["weekend"]

Eloquent JavaScript: Data

Error Handling

The try..catchsyntax allows us to ā€œcatchā€ errors so the script can, instead of dying, do something more reasonable.

try
{
    // code...

} 
catch (err)
{
    // error handling

}

It works like this:

  1. First, the code in try {...} is executed.
  2. If there were no errors, then catch(err) is ignored: the execution reaches the end of try and goes on, skipping catch.
  3. If an error occurs, then the try execution is stopped, and control flows to the beginning of catch(err). The err variable (we can use any name for it) will contain an error object with details about what happened.

Keep in mind that try..catch works synchronously, meaning directly after each other. The following error catching doesnā€™t work:

try
{
    setTimeout(() => noSuchName, 1000); // script will die here
} 
catch (err)
{
    alert( "No error here" );
}

The function parameter for setTimeout is executed later, when the engine has already left the try..catch construct and not in ā€œcatching modeā€ anymore.

The Error Object

When an error occurs, JavaScript generates an object containing the details about it. The object is then passed as an argument to catch:

try{} 
catch (err) // <-- the "error object", could use another name instead of err
{
    // error handling
}

For all built-in errors, the error object has two main properties:

  • name: Error name. For instance, for an undefined variable thatā€™s ā€œReferenceErrorā€.
  • message: Textual message about error details.

There are other non-standard properties such as stack available in most environments but we will not look into that.

try
{
    lalala; // error, variable is not defined!
}
catch(err)
{
    alert(err.name); // ReferenceError
    alert(err.message); // lalala is not defined

    // Can also show an error as a whole
    // The error is converted to string as "name: message"
    alert(err); // ReferenceError: lalala is not defined
}

Example

let json = "{ bad json }";

try
{
    let user = JSON.parse(json); // <-- when an error occurs...
    alert( user.name ); // doesn't work
}
catch (e)
{
    // ...the execution jumps here
    alert( "Our apologies, the data has errors." );
    alert( e.name );
    alert( e.message );
}

[javascript.info]

Functions

Functions in JavaScript can be quite special when for example created by a function expression or the function being anonymous. For now we only care about the most basic function declarations.

You declare functions in JavaScript as in the following examples (the alert() method displays an alert dialog in your browser):

function showMessage()
{
    alert( 'Hello everyone!' );
}

showMessage();
function showMessage(from, text) // arguments: from, text
{
    alert(from + ': ' + text);
}

showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)
function showMessage(from, text = "no text given")
{
    alert( from + ": " + text );
}

showMessage("Ann"); // Ann: no text given
function sum(a, b)
{
    return a + b;
}

let result = sum(1, 2);
alert( result ); // 3

Optional Arguments

The following code is allowed and executes without any problem:

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

console.log(square(4, true, "kittens"));

// console output:  16

We defined square with only one parameter. Yet when we call it with three, the language doesnā€™t complain. It ignores the extra arguments and computes the square of the first one.

JavaScript is extremely open-minded about the number of arguments you pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value undefined.

The downside of this is that it is possibl:likely, eve:that at some point youā€™ll accidentally pass the wrong number of arguments to functions. And no one cares.

The upside is that this behavior can be used to allow a function to be called with different numbers of arguments. For example, this minus function tries to imitate the - operator by acting on either one or two arguments:

function minus(a, b) 
{
    if (b === undefined) return -a;
    else return a - b;
}

console.log(minus(10));
// console output: -10
console.log(minus(10, 5));
// console output: 5

If you write an = operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given.

For example, this version of power makes its second argument optional. If you donā€™t provide it or pass the value undefined, it will default to two, and the function will behave like square.

function power(base, exponent = 2) 
{
    let result = 1;
    for (let count = 0; count < exponent; count++)
    {
        result *= base;
    }
    return result;
}

console.log(power(4));
// console output:  16

console.log(power(2, 6));
// console output:  64

Rest Parameters

The following is a fairly new feature, introduced with ECMAScript 2018.

It can be useful for a function to accept any number of arguments. For example, Math.max computes the maximum of all the arguments it is given.

To write such a function, you put three dots before the functionā€™s last parameter, as the following.

function max(...numbers)
{
    let result = -Infinity;
    for (let number of numbers)
    {
        if (number > result) result = number;
    }
    return result;
}
console.log(max(4, 1, 9, -2));
// console output: 9

When such a function is called, the rest parameter is bound to an array containing all further arguments. If there are other parameters before it, their values arenā€™t part of that array. When, as in max, it is the only parameter, it will hold all arguments.

You can use a similar three-dot notation to call a function with an array of arguments.

let numbers = [5, 1, 7];
console.log(max(...numbers));
// console output: 7

This ā€œspreadsā€ out the array into the function call, passing its elements as separate arguments. It is possible to include an array like that along with other arguments, as in max(9, ...numbers, 2).

Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array.

let words = ["never", "fully"];
console.log(["will", ...words, "understand"]);
// console output: ["will", "never", "fully", "understand"]
Resources

Pass by Reference vs. by Value

Javascript is pass-by-value for primitive datatype. This means that when you pass arguments to a function, JavaScript makes a copy of their values and works inside of the function with those copies. In case of arrays and objects, Javascripts mimics the behavior of pass-by-reference, which is under the hood again pass-by-value.

Higher Order Functions

In mathematics and computer science, a higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments,
  • returns a function as its result.

This means that functions operate on other functions, either by taking them as arguments or by returning them. As functions are regular values in JavaScript, they can be handled almost the same way.

Higher-order functions allow us to abstract over actions, not just values. They come in several forms. For example, we can have a function that creates a new function.

We actually have already used a higher order function by adding a function as callback to an event listener:

function setup() {

    button = createButton('submit');
    button.mousePressed(greet);
}

function greet() {
    // ...
}

[Wikipedia: Higher-order Function]

Three classical exemplary higher-order functions are map, filter, and reduce for working with arrays.

Each programming language supporting programming in the functional style supports at least the three functions map, filter, and reduce. The names of the three functions have variations in the different programming languages.

  • map applies a function to each element of its list
  • filter removes all elements of a list not satisfying a condition
  • reduce successively applies a binary operation to pairs of the list and reduces the list to a value.
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(getLength);

function getLength(item)
{
    return item.length;
}

console.log(lengths); // 5,7,6

Reduce syntax:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

let sum = [15.5, 2.3, 1.1, 4.7].reduce(getSum, 0);

function getSum(total, num) 
{
    return total + Math.round(num);
}
console.log(sum); // 24

mapfilterreduce
[Modernes Cpp]

There are two ways to make this code much more compact: anonymous functions and the most modern way: arrow functions.

Anonymous Functions

Anonymous functions are called anonymous because they arenā€™t given a name in the same way as normal functions. As the name is missing as connector to the function we can not refer to it from another place in the code. Hence, anonymous functions are directly placed, where they are needed (or stored in a variable, we come back to this).

An anonymous function is a function without a name.

This example:


function setup()
{
    let canvas = createCanvas(512, 512);
    canvas.doubleClicked(changeColor);

    background(240);
}

function changeColor()
{
    background(random(255), random(255), random(255));
}

can be rewritten to using an anonymous function as follows:

function setup()
{
    let canvas = createCanvas(512, 512);

    // The callback as anonymous function
    canvas.doubleClicked(function ()
    {
        background(random(255), random(255), random(255));
    });

    background(240);
}

The above makes use of this principle. The value of the first argument of the .doubleClicked() event is a function.

Using an anonymous function with map:

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(function (item)
{
    return item.length;
});
console.log(lengths); // 5,7,6

Anonymous function can also be stored in and invoked (called) by using a variable name.

const greatMath = function (a, b) {return a * b};
let result = greatMath(4, 3);

console.log(result);

This is also called a function expression. Function expressions stored in variables do not need function names. They are always invoked (called) using the variable name.

Function expressions and functions as arguments are possible because functions in Javascript are just a special type of object. This means they can be used in the same way as any other object. They can be stored in variables, passed to other functions as parameters or returned from a function using the return statement. Functions are always objects, no matter how they are created.

The subtle difference between a functions declaration (the ā€œnormalā€ way of declaring functions) and a function expression is when a function is created by the JavaScript engine:

  • A function expression is created when the execution reaches it and is usable only from that moment on.
  • A function declaration can be called earlier than it is defined as the function was stored in a pre-processing step.

For example, a global function declaration is visible in the whole script, no matter where it is. Thatā€™s due to internal algorithms. When JavaScript prepares to run the script, it first looks for global function declarations in it and creates the functions. We can think of it as an ā€œinitialization stageā€.

And after all function declarations are processed, the code is executed. So it has access to these functions.

For example, this works:

//Function Declaration

sayHi("Hans"); // Hello Hans

function sayHi(name)
{
    console.log("Hello " + name);
}

The Function Declaration sayHi is created when JavaScript is preparing to start the script and is visible everywhere in it.

If it were a Function Expression, then it wouldnā€™t work:

//Function Expression

sayHi("Hans"); // error!

const sayHi = function(name)
{
    console.log("Hello " + name);
};

[javaScript.info] [javascript.info]

Arrow Functions

Arrow functions allow an even shorter syntax for writing function expressions (starting with ECMAScript 2015) by eliminating in certain cases the function keyword, the return keyword, and even the curly bracketsā€¦ whhhhaaat? šŸ˜±

// ES5
const myFunction = function (param1, param2)
{
    // do something
}

becomes

// ECMAScript 2015
const myFunction = (param1, param2) =>
{
    // do something
}

An arrow comes after the list of parameters and is followed by the functionā€™s body. It expresses something like

this input (the parameters) produces this result (the body) in short as
this input => this result.

If there are no parameters, the () just stay empty:

// ES5
const myFunction = function ()
{
    // do something
}


// ECMAScript 2015
const myFunction = () =>
{
    // do something
}

With this, we can make the anonymous function for changing the canvas color in a p5 sketch even more compact:

// ES5
canvas.doubleClicked(function ()
{
    background(random(255), random(255), random(255));
});

// ECMAScript 2015
canvas.doubleClicked(() => background(random(255), random(255), random(255)));

If there is only one parameter, we can omit the ().

// ES5
const myFunction = function (param1)
{
    // do something
}

// ECMAScript 2015
const myFunction = param1 =>
{
    // do something
}
// ES5
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(function (item)
{
    return item.length;
});
console.log(lengths); // 5,7,6

// ECMAScript 2015
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);

One more function expression example:

// ES5
const result = function(x, y)
{
    x += 100;
    return x * y;
}

// ECMAScript 2015
const result = (x, y) =>
{
    x += 100;
    return x * y;
}

Also, when there is only one line of code, you can omit the return and the {}.

// ES5
const result = function(x, y) 
{
    return x * y;
}

// ECMAScript 2015
const result = (x, y) => x * y;

Once again, when there is only one parameter, you can also omit the parentheses around the parameter list.

// ES5
const result = function(x) 
{
    return x * x;
}

// ECMAScript 2015
const result = x => x * x;

For now you can remember that functions, anonymous functions, function expression and arrow functions do the same thing (they do have slight differences but nothing we need to be bothered about at this point). Arrow functions were added in 2015, mostly to make it possible to write function expressions more compactly.

[javaScript.info] [Eloquent JavaScript]

Closures

On a Side Note: this section about closures is advanced and optional!

The ability to treat functions as values brings up an interesting question. What happens to the (local) bindings or scope when the function call that created them is no longer active?

JavaScript variables can belong to the local or global scope. Global variables can be made local (private) with closures.

To understand this letā€™s take a bit of a detour.

Global Variables

A function can access all local variables defined inside the function:

function myFunction()
{
    let a = 4;
    return a * a;
}

But a function can also access variables defined outside the function:

let a = 4;
function myFunction()
{
    return a * a;
}

In the last example, a is a global variable. In a web page, global variables belong to the window object.

A local variable can only be used inside the function where it is defined. It is hidden from other functions and other scripting code.

Global and local variables with the same name are different variables. Modifying one, does not modify the other.

Variables created without the keyword let, are always global, even if they are created inside a function.

Variable Lifetime

Global variables live as long as your application (your window / your web page) lives.

Local variables have short lives. They are created when the function is invoked, and deleted when the function is finished.

A Counter Dilemma

Suppose you want to use a variable for counting something, and you want this counter to be available to all functions.

You could use a global variable, and a function to increase the counter:

// Initiate counter
let counter = 0;

// Function to increment counter
function add() 
{
    counter += 1;
}

// Call add() 3 times
add();
add();
add();

// The counter is now 3

There is a problem with the solution above: Any code on the page can change the counter, without calling add().

The counter should be local to the add() function, to prevent other code from changing it:

// Initiate counter
let counter = 0;

// Function to increment counter
function add() 
{
  let counter = 0; 
  counter += 1;
}

// Call add() 3 times
add();
add();
add();

// However, now the counter is 0

It did not work because we display the global counter instead of the local counter.

We can remove the global counter and access the local counter by letting the function return it:

// Function to increment counter
function add()
{
  let counter = 0; 
  counter += 1;
  return counter;
}

// Call add() 3 times
add();
add();
add();

// Now the counter is 1.

It did not work because we reset the local counter every time we call the function.

A JavaScript inner function can solve this.

Nested Functions

All functions have access to the global scope. In fact, in JavaScript, all functions have access to the scope above them.

JavaScript supports nested functions. Nested functions have access to the scope above them.

In this example, the inner function plus() has access to the counter variable in the parent function:

function add()
{
  let counter = 0;

  function plus() {counter += 1;}
  plus();    

  return counter; 
}

This could have solved the counter dilemma, if we could reach the plus() function from the outside.

We also need to find a way to execute counter = 0 only once.

We need a closure.

Setup


let add = function () 
{
    let counter = 0;
    return function ()
    {
        counter += 1; 
        return counter;
    }
};

let result = add();
console.log(result);

console.log(result());
console.log(result());
console.log(result());

// the counter is now 3

The function add returns not a value but a function expression.

This way result becomes a function. The miraculous part is that the function saved in result can access the counter in the parent scope.

The counter is protected by the scope of the anonymous function.

This is called a JavaScript closure. A closure is a function having access to the parent scope, even after the parent function has closed.

The key to remember is that when a function gets declared, it contains a function definition and a closure.

The closure is a collection of all the variables in scope at the time of creation of the function.

A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called. Or imagine this as a backpack. A function definition comes with a little backpack. And in its pack it stores all the variables that were in scope at the time that the function definition was created.

The following code shows another example of this. It defines a function, wrapValue, that creates a local binding. It then returns a function that accesses and returns this local binding.

function wrapValue(n) 
{
    let local = n;
    return () => local;

    //   return function()
    //   {
    //       return local;
    //   }
}

let wrap1 = wrapValue(1);
let wrap2 = wrapValue(2);

console.log(wrap1());
// console output:  1

console.log(wrap2());
// console output:  2

This works as youā€™d hopeā€”both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls canā€™t trample on one anotherā€™s local bindings.

With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount.

function multiplier(factor) 
{
    return number => number * factor;

    // return function(number)
    // {
    //     return number * factor;
    // }
}

let twice = multiplier(2);
console.log(twice(5));
// console output:   10

let triple = multiplier(3);
console.log(triple(5));
// console output:   15

In the example, multiplier is called and creates an environment in which its factor parameter is bound to 2. The function value it returns, which is stored in twice, remembers this environment. So when that is called, it multiplies its argument by 2.

Thinking about programs like this takes some practice.

[Eloquent JavaScript: Functions] [w3schools.com: JavaScript Closures]

Asynchronism

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk).

program_flow[source]

Callbacks

One approach to asynchronous programming is to make functions that perform a slow action take an extra argument, called a callback function. The action is started, and when it finishes, the callback function is called with the result.

Or, you can use callbacks to create dependencies between functions (if this, then thatā€¦):

function setup()
{
    let canvas = createCanvas(512, 512);
    canvas.doubleClicked(changeColor);

    background(240);
}

function changeColor()
{
    background(random(255), random(255), random(255));
}

Or better (see the arrow function syntax):


function setup()
{
    let canvas = createCanvas(512, 512);
    canvas.doubleClicked(() => background(random(255), random(255), random(255)));
    background(240);
}

functions_01
[pinterest]

Error-First Callbacks

Functions have not only the option to hand data back and forth but also errors. This means that callback functions get both, an error and data as input arguments.

Error first callbacks is a convention common to many JavaScript libraries. For example, most asynchronous methods exposed by the server environment Node.js core API follow the error-first callback pattern. However, the language JavaScript itself does not enforce this pattern.

In the error-first callback pattern is:

  1. the first argument of the callback reserved for an error object. If an error occurred, it will be returned by the first err argument.
  2. the second argument of the callback reserved for any successful response data.

If no error occurred, err will be set to null and any successful data will be returned in the second argument.

[Fred Schott] [nodejs.org]

const fs = require('fs');

function errorFirstCallback(err, data)
{
    if (err) 
    {
        console.error('There was an error', err);
        return;
    }
    console.log(data);
}

fs.readFile('./does_not_exist', 'utf8', errorFirstCallback);
fs.readFile('./does_exist', 'utf8', errorFirstCallback);

Keep in mind that the above is not a client side function, recognized by the browser, but it is server side NodeJS code (we will come back to this).

The callback functionality is based on the principle of higher order functions.

Nesting Callbacks

Letā€™s say we have a function for loading a script with a callback function, which is executed with the script is loaded:

loadScript('script1.js', () => {console.log("ho ho ho, happy holidays")});

Now, we want to load to scripts sequentially, meaning one after the other. The natural solution would be to put the second loadScript call inside the callback, like this:

loadScript('script1.js', () => {

    console.log("ho ho ho, happy holidays")

    // Loading a second script
    loadScript('script2.js', () => {console.log("...and a happy new year!")});
});

After the outer loadScript is complete, the callback initiates the inner one.

What if we want one more scriptā€¦?

loadScript('script1.js', () => {

    console.log("ho ho ho, happy holidays")

    // Loading second script
    loadScript('script2.js', () => {

        console.log("...and a happy new year!")
    
        // Loading a third script
        loadScript('script3.js', () => {console.log("Now it is back to work.")});
    });
});

Can this be good?

[javascript.info]

Pyramid of Doom

At a first glance, nesting callbacks is a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it is just fine.

But letā€™s image the above example as error-first callbacks:

loadScript('script1.js', (err, script) => {

    if (err)
    {
        handleError(err);
    } 
    else
    {
        console.log("ho ho ho, happy holidays")

        // Loading second script
        loadScript('script2.js', (err, script) => {
        if (err)
        {
            handleError(err);
        }
        else
        {
            console.log("...and a happy new year!")

            // Loading a third script
            loadScript('script3.js', (err, script) => {
            if (err)
            {
                handleError(err);
            }
            else
            {
                console.log("Now it is back to work.");
            }
        }
    }
});

Ahhhhā€¦ šŸ˜³

As calls become more nested, the code becomes deeper and increasingly more difficult to manage. This ultimately might lead to a so-called pyramid of doom. The pyramid of nested calls grows to the right with every asynchronous action. Soon it spirals out of control.

This way of coding isnā€™t very good ā˜šŸ»

doom
[javascript.info]

For this example it would actually be worthwhile to go back to making every action a standalone function once again, which is more readable and easier to handle:

loadScript('script1.js', step1);

function step1(error, script) 
{
  if (error) 
  {
    handleError(error);
  } else 
  {
    // ...
    loadScript('script2.js', step2);
  }
}

function step2(error, script) 
{
  if (error) 
  {
    handleError(error);
  } else 
  {
    // ...
    loadScript('script3.js', step3);
  }
}

function step3(error, script) 
{

  if (error) 
  {
    handleError(error);
  } else 
  {
    // ...continue after all scripts are loaded (*)
  }
};

Luckily, there are other ways to avoid such pyramids. One of the best ways is to use promises.

[javascript.info]

Promises

On a Side Note: The following chapter is a tiny bit advanced in its level of detail. Make sure that you understand the general concept of promises and how to use then & catch and async & await (as they might becoming from a given library or framework). As a beginner you do not need to know how to write Promise objects yourself.

A promise is an asynchronous action that may complete at some point and produce a value. A promise object is able to notify anyone who is interested when its value is available.

Promises provide a clean and elegant syntax and methodology to handle async calls.

Understanding Promises

I promise to bring you cake by next week.

You donā€™t know if you will actually get that cake by next week or not. I can either really bring cake, or stand you up and withhold the cake if I donā€™t feel like it.

That is a promise. A promise has 3 states. They are:

  • Pending: You know about the promise but donā€™t know if you will get that cake.
  • Fulfilled: I brought you cake.
  • Rejected: I didnā€™t feel like baking and I didnā€™t bring any cake afterall.

Creating a Promise

The general constructor syntax for a promise object is:

let promise = new Promise((resolve, reject) =>
{
    // executor (the producing code, "promise of cake")
});

The function passed to new Promise is called the executor. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above, the executor is me bringing you cake.

The resulting promise object has two internal properties:

  • state: initially pending, then changes to either fulfilled or rejected,
  • result: an arbitrary value of your choosing, initially undefined.

When the executor finishes the job, it should call one of the functions that it gets as arguments:

  • resolve(value): to indicate that the job finished successfully
    • sets state to fulfilled
    • sets result to value
  • reject(error): to indicate that an error occurred
    • sets state to rejected
    • sets result to error

[source]

In the following, an example of a Promise constructor and a simple executor function with its producing code.

On a Side Note: The setTimeout function, available both in Node.js and in browsers, waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function.

let lenaFeelsLikeBacking = true;

// Promise
let promiseOfCake = new Promise( (resolve, reject) =>
    {
        if (lenaFeelsLikeBacking) 
        {
            let cake = 
            {
                type: 'chocolate',
                taste: 'superdelicious'
            };
            // this emulates that the baking takes some time...
            setTimeout(() => resolve(cake), 1000); 
        } 
        else 
        {
            let reason = new Error('Lena didn\'t feel like baking');
            reject(reason);
        }

    }
);

There can be only a single result or an error.

The executor must call only one resolve or reject. The promiseā€™s state change is final.

All further calls of resolve and reject are ignored:

let promise = new Promise((resolve, reject) =>
{
    resolve("done");

    reject(new Error("ā€¦")); // ignored
    setTimeout(() => resolve("ā€¦")); // ignored
});

Also, resolve/reject expect only one argument and will ignore additional arguments.

Consumers: then and catch

A Promise object serves as a link between the executor (the ā€œproducing codeā€ or ā€œLena might bakeā€) and the consuming functions (you, waiting for cake), which will receive the result or error.

Consumer functions can be registered (subscribed) using the methods .then and .catch.

The syntax of .then is:

promise.then(
  (result) => { /* handle a successful result */ },
  (error)  => { /* handle an error */ }
);

The first argument of .then is a function that:

  1. runs when the Promise is resolved, and
  2. receives the result.

The second argument of .then is a function that:

  1. runs when the Promise is rejected, and
  2. receives the error.
let lenaFeelsLikeBacking = true;

// Promise
let promiseOfCake = new Promise( (resolve, reject) =>
    {
        if (lenaFeelsLikeBacking) 
        {
            let cake = 
            {
                type: 'chocolate',
                taste: 'superdelicious'
            };
            setTimeout(() => resolve(cake), 1000);
        } 
        else 
        {
            let reason = new Error('Lena didn\'t feel like baking');
            reject(reason);
        }

    }
);

// Call the promise
promiseOfCake.then
(
    result => console.log(result),
    error => console.error(error),
);

If weā€™re interested only in successful completions, then we can skip the error function argument to .then:

promiseOfCake.then(result => console.log(result));

If weā€™re interested only in errors, then we can use null as the first argument:

.then(null, errorHandlingFunction)

promiseOfCake.then(null, error => console.error(error));

Or we can use catch, which is exactly the same:
.catch(errorHandlingFunction)

promiseOfCake.catch(error => console.error(error));

The call .catch(f) is a completely the same to .then(null, f) - itā€™s just a shorthand.

What you will see most is the following syntax:

promiseOfCake
.then(result => console.log(result))
.catch(error => console.error(error));

Chaining Promises

Sequencing

Letā€™s return to the problem mentioned in the section about callbacks. We have a sequence of tasks to be done one after another.

new Promise((resolve, reject) =>
{
    setTimeout(() => resolve(1), 1000);

}).then((result) => 
{ // (**)

  console.log(result); // 1
  return result * 2;

}).then((result) => 
{ // (***)

  console.log(result); // 2
  return result * 2;

}).then((result) => 
{
  console.log(result); // 4
  return result * 2;

});

The idea is that the result is passed through the chain of .then handlers.

Here the flow is:

  1. The initial promise resolves and returns the value 1 (*),
  2. Then the .then handler is called (**).
  3. The value that it returns is passed to the next .then handler (***)
  4. ā€¦and so on.

As the result is passed along the chain of handlers, we can see a sequence of logs: 1 ā†’ 2 ā†’ 4.


[javascript.info]

On the return of a call to promise.then it returns an arbitrary thenable object, which is treated the same way as a promise. On that thenable object we can call .then again.

When a promise becomes resolved and returns a value, the next .then is called with that value.

Returning Promises

Letā€™s say you are promising your friends some of the cake I promised to bring to class. For this we need to create an additional promise.

Multiple promises can be chained if the returned value of a .then handler is once again a promise.

If the returned value is a promise, then the further execution is suspended until that returned promise settles. After that, the result of that promise is given to the next .then handler.

For instance:

new Promise((resolve, reject) =>
{
    setTimeout(() => resolve(1), 1000);

}).then(result =>
{

    console.log(result); // 1

    return new Promise((resolve, reject) => 
    { // (*)
        setTimeout(() => resolve(result * 2), 1000);
    });

}).then(result =>
{ // (**)

    console.log(result); // 2

    return new Promise((resolve, reject) => 
    {
        setTimeout(() => resolve(result * 2), 1000);
    });

}).then(result =>
{
    console.log(result); // 4
});

Here

  • the first .then logs 1 and returns new Promise(ā€¦) in the line (*).
  • After one second it resolves, and the result (the argument of resolve, here itā€™s result*2) is passed on to the handler of the second .then in the line (**). It logs 2 and does the same thing.

So the output is again 1 ā†’ 2 ā†’ 4, but now with 1 second delay between alert calls.

Returning promises allows us to build chains of asynchronous actions.

Example Promising Cake

You can only fullfil your promise of cake to your friends after the promiseOfCake is fulfilled by me.

let youGiveOutCake = true;
// let youGiveOutCake = false;


let promiseOfPassingOnCake = cake =>
{
    let promise = new Promise((resolve, reject) =>
        {
            if (youGiveOutCake) 
            {
                let message = 'Hey friend, I have ' + cake.taste + ' '
                            + cake.type + ' cake for you.';
                setTimeout(() => resolve(message), 1000);
            } 
            else 
            {
                let reason = new Error('You are selfish.');
                reject(reason);
            }
        }
    )
    return promise;
}

// Chain multiple promises
promiseOfCake
    .then(promiseOfPassingOnCake)
    .then(fulfilled => console.log(fulfilled))
    .catch(error => console.log(error.message));

Promise chaining is great at error handling. When a promise rejects, the control jumps to the closest rejection handler down the chain. Thatā€™s very convenient in practice.

There is even an implicit error handling. The code of the executor and promise handlers has an invisible try..catch around it. If an error happens, it gets caught and treated as a rejection.

We may have as many .then as we want, and then use a single .catch at the end to handle errors in all of them.

For instance, this code:

new Promise((resolve, reject) =>
{
    throw new Error("Whoops!");

}).catch(error); // Error: Whoops!

ā€¦Works the same way as this:

new Promise((resolve, reject) =>
{
    reject(new Error("Whoops!"));

}).catch(error); // Error: Whoops!

The invisible try..catch around the executor automatically catches the error and treats it as a rejection.

Similarly, if we throw inside .then handler an error, meaning we reject a promise, the control jumps to the nearest error handler.

new Promise((resolve, reject) =>
{
    resolve("ok");

}).then((result) =>
{
    throw new Error("Whoops!"); // rejects the promise

}).catch(error); // Error: Whoops!

Thatā€™s so not only for throw, but for any errors, including programming errors as well:

new Promise((resolve, reject) =>
{
    resolve("ok");
}).then(result =>
{
    blabla(); // no such function

}).catch(error); // ReferenceError: blabla is not defined

The final .catch not only catches explicit rejections, but also occasional errors in the handlers above.

Example loadScript

Letā€™s come back to the example of loading three scripts after each other.

function loadScript(src)
{
    return new Promise((resolve, reject) =>
    {
        let script = ...

        if(script) resolve(script)
        else reject(new Error("Script load error.");
    });
}

loadScript("script_one.js")
    .then(script => loadScript("script_two.js"))
    .then(script => loadScript("script_three.js"))
    .then(script => 
    {
        // use functions declared in the scripts
        one();
        two();
        three();
    })
    .catch((error) => console.log(error.message));

Here each loadScript call returns a promise, and the next .then runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.

We can add more asynchronous actions to the chain. Please note that this code is flat, it grows down, not to the right. There are no signs of a pyramid of doom, which is what we want.

Please also note that technically it is possible to write .then directly after each promise, without returning them, like this:

loadScript("script_one.js").then(script1 => 
{
    loadScript("script_two.js").then(script2 =>
    {
        loadScript("script_three.js").then(script3 => 
        {
            // this function has access to variables script1, script2 and script3
            one();
            two();
            three();
        });
  });
});

This code does the same: it loads 3 scripts in sequence. But it grows to the right. So we have the same problem as with callbacks. Use chaining (return promises from .then) to evade it.

There are cases where itā€™s ok to write .then directly, but thatā€™s an exception rather than a rule.

[javascript.info]

Summary then & catch


[javascript.info]

Once again, if this was confusing, focus for now on knowing what then and catch are and how to use them. You will need this for working with given functions returning promises, coming from a library or a framework. However, you will not need to write promises yourself any time soon.

Async/Await

Now that I tortured you with the core functionality of promises, I am also telling you that there is actually an easier syntax for working with promisesā€¦ because that is just how I am šŸ˜Ž. However, this is fairly new syntax and not that widespread in the world of web developments yet.

Async

When placing the keyword async in front of a function, the function automatically returns a promise. Specifically, whatever the function returns, becomes wrapped in a resolved promise.

On a Side Note: alert shows a message and waits for the user to press the ok-button. Used as in the example below, it automatically shows the return value passed from myFunc.

async function myFunc()
{
    return 1;
}
myFunc().then(alert); // 1

The above is the same as:

function myFunc()
{
    return Promise.resolve(1);
}
myFunc().then(alert); // 1

Await

With the keyword await you can postpone the execution of code in an async function until a promise returns its result.

async function myFunc()
{

    let promise = new Promise((resolve, reject) =>
    {
        setTimeout(() => resolve("done!"), 1000)
    });

    let result = await promise; // wait until the promise resolves (*)
    alert(result); // "done!"
}

myFunc();

The function execution ā€œpausesā€ at the line (*) and resumes when the promise settles, with result becoming its result. So the code above shows ā€œdone!ā€ in one second.

await suspends the function execution until the promise settles, and then resumes it with the promise result.

Keep in mind that await only works inside of a async function!

If a promise resolves normally, then await promise returns the result. But in the case of a rejection, it throws the error, just as if there were a throw statement at that line.

async function myFunc()
{
    throw Error("Whoops!");
}

The above is the same as:

function myFunc()
{
    await Promise.reject(Error("Whoops!"));
}

In real-world situations, the promise may take some time before it rejects. In that case there will be a delay before await throws an error.

We can catch that error using try..catch, the same way as a regular throw inside of the function:

async function myFunc()
{
    try {
        let response = await (Error("Whoops!"));
    } catch(err) {
        alert(err); //Whoops!
    }
}

In the case of an error, the control jumps to the catch block. We can also wrap multiple lines:

async function myFunc()
{
    try {
        let response1 = await (Error("Whoops!"));
        let response2 = await (Error("Whoops Again!"));
    } catch(err) {
        alert(err); // catches both errors
    }
}

Or, if we donā€™t do the error catching inside of the function, we can catch the return of the function:

async function myFunc()
{
    return await (Error("Whoops!"));
}
myFunc().catch(alert); //Whoops!

[javascript.info]

Example

We can re-write the following example code, which is chaining Promises to using async/await instead of .then/catch:

On a Side Note: fetch() allows you to make (HTTP) requests to servers from web browsers and returns a promise. E.g. let response = fetch(url);.

function loadJson(url)
{
    return fetch(url)
        .then(response =>
        {
            if (response.status == 200)
            {
                return response.json();
            }
            else
            {
                throw Error(response.status);
            }
        });
}

loadJson('no-such-user.json')
    .catch(alert); // Error: 404

As async function the above becomes:

async function loadJson(url)  // (1.)
{
    let response = await fetch(url); // (2.)

    if (response.status == 200) {
        return response.json(); // (3)
    }

    throw Error(response.status);
}

loadJson('no-such-user.json')
    .catch(alert); // Error: 404 (4.)
  1. The function loadJson becomes async.
  2. All .then inside are replaced with await.
  3. The if statement waits for the promise from fetch to resolve before it is executed.
  4. The error thrown from loadJson is handled by .catch. We canā€™t use await loadJson(ā€¦) there, because weā€™re not in an async function.

Summary

The async keyword before a function has two effects:

  1. Makes it always return a promise.
  2. Allows await to be used in it.

The await keyword before a promise makes the JavaScript engine wait until that promise settles, and then:

  1. If itā€™s an error, the exception is generated ā€” same as if throw error were called at that very place.
  2. Otherwise, it returns the result.

Together they provide a great framework to write asynchronous code that is easy to both read and write.

With async/await we rarely need to write promise.then/catch, but we still shouldnā€™t forget that they are based on promises, because sometimes, when not inside of an async function, we have to use these methods.

[javascript.info]

Modules

JavaScript modules allow you to break up your code into separate files. This makes it easier to maintain the code-base. JavaScript modules rely on the import and export statements.

Keep in mind, that modules only work with the HTTP(s) protocol. A web-page opened via the file:// protocol cannot use import / export.

Export

You can export a function or variable from any file. Exported values can then be imported into other programs with the import declaration.

There are two types of exports, named exports and default exports. You can have multiple named exports per module but only one default export.

Named Exports

You can create named exports for specific lines individually, or all at once at the bottom of a file.

For an in-line individual export, after the export keyword, you can use let, const, and var declarations, as well as function or class declarations:

// person.js
export const name = "Jesse";
export const age = 40;

For exporting all at once at the bottom, you can also use the export { name1, name2 } syntax to export a list of names declared elsewhere:

// person.js
const name = "Jesse";
const age = 40;

export {name, age};

Default Exports

You can only have one default export in a file.

// message.js
const message = () => {
    const name = "Jesse";
    const age = 40;
    return name + ' is ' + age + 'years old.';
};

export default message;

Named exports are useful when you need to export several values. When importing this module, named exports must be referred to by the exact same name (optionally renaming it with as), but the default export can be imported with any name. For example:

// file test.js
const k = 12;
export default k;
// some other file
import m from './test'; // note that we have the freedom to use import m instead of import k, because k was default export
console.log(m);        // will log 12

You can also rename named exports to avoid naming conflicts:

export {
  myFunction as function1,
  myVariable as variable,
};

Import

You can read-only import modules into a file in two ways, based on if they are named exports or default exports. Named exports are constructed using curly braces. Default exports are not.

In total there are four forms of import declarations:

  • Named import: import { export1, export2 } from ā€œmodule-nameā€;
  • Default import: import defaultExport from ā€œmodule-nameā€;
  • Namespace import: import * as name from ā€œmodule-nameā€;
  • Side effect import: import ā€œmodule-nameā€;

import declarations can only be present in modules, and only at the top-level (i.e. not inside blocks, functions, etc.).

Import From Named Exports

Import named exports from the file person.js:

import { name, age } from "./person.js";

Import From Default Exports

Import a default export from the file message.js:

import message from "./message.js";

Style Guide

I highly recommend to early on pick a style you want to follow when developing code. This is a personal choice but there are several standards developed by JavaScript communities. It is alway a good idea to follow a standard.

As an example on what people can become hang up upon in the following the spaces vs. tabs discussion.

Spaces and Tabs

The tabs vs. space discussion is as long as time itself.

Here the evaluation of 400,000 GitHub repositories, 1 billion files, 14 terabytes of code:

tabs_vs_space

[400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?]

Arguments for tabs

  • If you use tabs instead of spaces, you can configure in most IDEs how much indentation each space; you can set the value to four spaces, while someone who insists on two spaces can also be satisfied.

Arguments for spaces

  • With only two spaces more code can be fit on to one line and still fit within the screen, without horizontal scrolling.
    • ā€œCallback hellā€ is real in JavaScript and the average program have many more indents than the average C or PHP program.
  • Keeps the file size down by 4%, since unlike many other languages, JavaScript typically isnā€™t compiled and often not compressed before being sent to the user.
  • Other languages have a standard with four spaces, and for this reason, many developers still use four spaces in JavaScript as a habit.

[Stackoverflow: Coding Style in JavaScript - Why prefer 2 spaces not 4 spaces?]

Resources

The End

šŸ’ŖšŸ¼ šŸ—“ šŸ”Ž