javascript

data types

Principal JS types:

  • number: 13
  • string: 'Renodor'
  • boolean: true
  • array: [13, 'Renodor', True]
  • object: { name: 'Renodor', age: 13 }
  • null: null
  • undefined: undefined

To know the type of something you can use typeof method:

typeof(13) // returns 'number'
typeof('Renodor') // returns 'string'
typeof(true) // returns 'boolean'

(It won't work with array or null though...)

To convert data types:

  • String to number: Number.parseInt('13')
  • Number to string: (13).toString()

strings

A string can be define with ' : 'Hello', or " : "Hello"

Common built-in string methods:

  • .toUpperCase() : returns an uppercase version of string
  • [0] : returns the first letter of string
  • .substring(3) : cuts string at the 3rd letter (exluding it), and returns the rest
  • .substring(2, 4) : cuts string at 2nd letter (exluding it), and returns the rest until reaching 4th letter (including it)
  • .split(',') : split string between , into an array ("a,b,c".split(',') returns [a, b, c]
  • .length : returns sting length (This is actually not a function but an attribute, so we don't need the parenthesis)

String Interpolation: to insert a variable within a string without using the "+". You need to wrap your string with backticks: ` and interpolate code with ${}

const myPet = "armadillo";
console.log(`I own a pet ${myPet}.`);
// outputs: "I own a pet armadillo."

variables

var : variable. Scope is global or local to an entire function (regardless of block scope)

let : variable that can be reassigned. Limited to the scope of a the block statement (block-scoped)

const : variable that can't be reassigned. (But value it holds can change, for example if its an object, you can change elements of the objects but not reassign it to a whole new object)

let favFood = "Pizza";
console.log(favFood) // outputs: "Pizza"
favFood = "Pasta"
console.log(favFood); // outputs: "Pasta"

const name = "Renodor";
console.log(name); // outputs: "Renodor"
name = "Pas Renodor"
console.log(favFood); // outputs: "TypeError"

Naming convention: in JS variables are lower camelCase: myVariable

Tips: x = x + 1 can also be written : x += 1
Same for -=, *=, /=
Same for ++, --, //, **, it means adding, subtract, divise or multiply by 1

math

In JavaScript, to get a random number between 0 and 1, use the Math.random() function:
console.log(Math.random()); >>> will outputs for ex : 0.5408145050563944

Then if you want a random number between 1 and 10, just multiply the result of Math.random by 10, then round up or down:

  • Math.floor(Math.random() * 10) >> will round down to a whole number
  • Math.ceil(Math.random() * 10) >> will round up to a whole number
  • Math.round(Math.random() * 10) >> will round to the nearest whole number

conditional statements

Truthy / Falsy Values: If you use an if on a non-bolean value (a string or a number for example), this value is neither "true" nor "false". But it is "truthy" or "falsy":

  • It is "truthy" (so will return true) if it exists / is defined
  • It is "falsy" (so will return false) if:
    • it doesn't exist / is not defined : null
    • it is empty : ""
    • it is equal 0
    • it is equal : NaN (Not a Number)
let numberOfApples = 0;
if (numberOfApples) {
   console.log('Let us eat apples!');
} else {
   console.log('No apples left!');
}
// outputs: "No Apples Left !"

Short-circuit Evaluation: The pipe comparison tool || can replace an if. Indeed it will return option "A" OR "B". So it will test first the first option. So if "A" exists, it takes "A", otherwise it takes "B". So it works exactly like an if.

let defaultName;
if (username) {
   defaultName = username;
} else {
defaultName = 'Stranger';
}

// is the same as
let defaultName = username || 'Stranger'

functions

Naming convention: in JS methods are lower camelCase: myFunction

// To declare it
function functionName() {
  console.log('blablabla');
}

// To call it
functionName();

Function have "hoisting" caracteristic, meaning you can call it (and use it) before declaring it (even if it is not a good practice).

Since ES6 you can define a default value of a function parameter.

function greeting(name = 'stranger') {
  console.log(`Hello, ${name}!`);
}
// Will return "Stranger" if name is not defined

Return: by default a fonction will always return undefined. Even when it work perfectly. In order for a function to return a result you need to use the keword return. With that I tell the function what value it has to return.

function calculateArea(width, height) {
  const area = width*height;
  return area;
}

The return kw also allows you to stop the execution of a function. Indeed a function will stop when it meets return:

function getRectArea(width, height) {
  if (width > 0 && height > 0) {
    return width * height;
  }
  return 0;
}

getRectArea(3, 4); // return 12

getRectArea(-3, 4); // return 0

Function Expression: Allows you to store an anonymous function inside a variable and then call this function by calling the variable. You can also add arguments directly inside the variable (we usually use the const kw for that):

const variableA = function(arg1, arg2) {
  'Blablabla';
};
variableA(arg1, arg2)

Warning: a function expression is not "hoisted". You can't call it before declaring it

Arrow functions: Since ES6, arrow functions synthax allows to define a function without using the "function" kw:

const rectangleArea = (width, height) => {
  return width*height;
};

Arrow functions specificities:

  • Functions with only 1 argument doesn't need parenthesis: const varA = arg1 => { ... }; (if there are 0 or more than 1 argument you do need parenthesis).
  • Functions that only have 1 statement (one line of code) doesn't need braces nor return (its called "implicit return"): const sum = number => number + number;
  • This binding: arrow functions does not have its own binfing to this. With traditional functions, if you have a function inside a function, the this (referring to the function itself) will be lost in the nested function… So you needed to do : var that = this (to store the this somewhere to use it afterward). With arrow functions, the "context" of the "this" is preserved. So if you use an arrow function inside another function, the this of the arrow function will be the one of the higher function.

scope

It defines a "perimeter" on which you can use a variable. If variable is not define inside a function (outside the { } ), it belongs to the "global scope". It is then a "global variable", you can access it everywhere.

On the contrary if a variable is defined inside a function (inside the block), it belongs to the scope of this function, and you can use it only inside this function. It has a "block scope". It is then a "local variable".

If you have to many global variable, it creates "scope polution". All those variable can have indesirable effect everywhere in your code. It is better to have few global variables and to localize it.

You can have two identical variable with different values depending on which scope you are looking

arrays

In JS an Array is defined with the [] notation: const hobbies = ['surf', 'teuf', 'comic books'] (here it is 3 strings but it can be all sorts of elements)

Index: you can then use each elements of an array calling its index. The first one have the index "0": hobbies[1] >> will return "teuf"

Length: return the number of elements in the array. hobbies.length == 3

(Btw you can use the same logic to return letters of a string: 'renodor'[2] >> will return "n")

Common built-in array methods:

  • .push(x) : add the value x at the end of the array
  • .pop() : remove the last element of the array (doesn't take any argument, will only remove the last element)
  • .shift() : remove the first element of the array (doesn't take any argument, will only remove the first element)
  • .unshift(x) : add the value x at the beginning of an array
  • .indexOf(x) : return the index of the first element x found in the array. (return -1 if the value is not present)
  • .splice(x, 1) : remove the element at the x position
  • .splice(x, y, z) : x = where to start removing/replacing elements. y = how many elements to remove. z = element to add (can have more than one). Ex:
    • .splice(0, 0, 'a', 'b') : will add 'a' and 'b' at the beginning of array
    • .splice(0, 1, 'a', 'b') : will add 'a' at the beginning of array and replace first element of array with 'b'
    • .splice(0, 2, 'a', 'b') : will replace first element of array with 'a' and 2nd element of array with 'b'
  • .slice(x, y) : return only a part of the array. Starts with the elment with index x (included) and stops at the element with index y (not included). It doesn't modify the original array, it just return a shorter copy. (Ex: array1 = ['pomme', 'bannane', 'fraise', 'chocolat'].slice(1, 3) will return ['banane', 'fraise'])
  • .join() : return a string with the concatenate elements of the array separated by comas. Can take an argument that specifies how to separate the elements of array: Ex:
    • .join('') : nothing between elements of array
    • .join(' ') : a space between elements of array
    • .join('-') : a dash between elements of array
  • array1.concat(array2) : return a new array with concatenate values of both arrays (don't modify original arrays, create a new one)
  • 'renodor'.trim() : remove space before and after a string (not an array method but...)

loops

For Loop:

for (initialisation; stop condition; to execute at the end of each iteration) {   code to execute; }

The "stop condition" is a boulean. The loop continues as long as this bolean is true. And it stops when the boulean is false.

Ex:

const grades = [10, 5, 13, 20, 18];
let sum = 0
for (let i = 0; i < grades.length; i++) {
  sum = sum + grades[i];
}
console.log(sum) // outputs 66 >> (10 + 5 + 13 + 20 + 18)

While Loop:

while (stop condition) {   code to execute; }

You just specify the stop condition. You have to define the initialisation before and the iteration within the code to execute

Ex:

const grades = [10, 5, 13, 20, 18];
let sum = 0
let i = 0

while (i < grades.length) {
  sum = sum + grades[i];
  i++;
}
console.log(sum) // outputs 66 >> (10 + 5 + 13 + 20 + 18)

Do...While Loop

do {   code to execute; } while (stop condition)

It is similar to the while loop but you specify the code to execute before. So it will execute at least one time (and until the stop condition is false). Whereas a normal while loop can sometimes never run if the stop condition is always false.

Ex:

const grades = [10, 5, 13, 20, 18];
let sum = 0
let i = 0

do {
  sum = sum + grades[i];
    i++;
} while (i < grades.length);
console.log(sum) // output 66 >> (10 + 5 + 13 + 20 + 18)

Break: the break kw allows you to force a loop to stop (even if the stop condition is still true). Inside the block code of your loop you have to add an if condition like that : if (condition) { break; }

(A loop within a loop is called a "nested loop").

high order functions

You can assign a fonction to a variable:

const functionA = () => {
  code;
};
const variableA = functionA; // >> You do it without parenthesis;

And so you can then call the function by calling the variable like that: variableA(); (You can even pass arguments to it.)

In JS, functions, like variables, are "first class objects", so like other objects you can assign proprieties to it like (.length() .name(), .toString() etc...)

You can also pass a function as an argument to another function. You call those functions "call back" because they are called during the execution of the high-order function.

When you pass a function as an argument you don't want to call this function. By calling it you would have the result of the function as an argument (and not the function itself). That is why when you put a function as argument you put it without the parenthesis.

iterators

Iterators are built-in array methods that allows you to iterate (loop) easily on arrays

.forEach() : apply the same function to all the elements of an array.

Ex:
const fruits = ['pommes', 'poires', 'oranges', 'kiwi']

fruits.forEach(fruit => {
  console.log(`- ${fruit}`);
});

>>> Will apply the console.log() to each elements of 'fruits'. (So we can say that forEach(); takes a call-back function as an argument.)

This function doesn't create a new array or change the original array. It just apply the function to each elements of the array. And it doesn't return anything, it always return undefined.

.map() : also apply a function to all elements of an array, but this time it returns a new array (with the new values). So you can assign the map method to a new variable to store the array that will be created during the execution.

Ex:
const numbers = [1, 2, 3, 4, 5];
const bigNumbers = numbers.map(number => {
  return number * 10;
});
// bigNumbers == [10, 20, 30, 40, 50]

.filter() : Apply a statement to all elements of an array, and return a new array with only the elements for witch this statement returned true

Ex:
const words = ['chien', 'chat', 'poussin', 'trotinette', 'hiiiibou'];
const smallWords = words.filter(word => {
  return word.length < 6;
}); // smallWords == ['chien', 'chat']

.findIndex() : apply a statement to all elements of an array, and return the index of the first element that return true. (It stops at the first it finds).

Ex:
const numbers = [40, 500, 230, 4, 9, 1] ;
const smallNumbers = numbers.findIndex(number => {
  return number < 10;
});
// smallNumbers will return 3 because this is the index of "4",
// the first number inferior to 10 it finds.

If .findIndex() doesn't find any element that fulfill the condition, it will return -1

.reduce() : apply a function to all elements of an array, and return only one value. It takes 2 parameter: an accumulator and the currentValue.

Initially the accumulator is the index 0 of the array, and the currentValue is the index 1 of the array. It will then loop over the array and apply a function using this two parameters. So during the second loop for example, the accumulator will be the returned value of the first loop, and the currentValue will be the index 3 of the array, etc...

Ex
const numbers = [1, 2, 4, 10];
const summedNumbers = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
});

So here, initially:

  • accumulator = 1
  • currentValue = 2

So the first operation will be 1 + 2
Then the function do it again. The new accumulator is 3 (1 + 2), and the new currentValue is 4, so the second operation will be 3 + 4
Etc...
So at the end, summedNumbers will return 17.

You can also add a second argument to the reduce method, which will define an initial value to the accumulator. (Other than the index 0 of the array). So it will add a new step to the operation.

const numbers = [1, 2, 4, 10];
const summedNumbers = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue
 }, 100)

Instead of starting with accumulator = 1, you start with accumulator = 100. And so initialy, currentValue is 1. After that, it is the same:

  • 100 + 1 = 101
  • 101 + 2 = 103
  • 103 + 4 = 107
  • 107 + 10 = 117 >>> final value of summedNumbers

some() : will look if at least one element of the array fulfill the condition, and will return true or false

every() : will look if all the elements of the array fulfill the condition, and will return true or false

objects

In JS an Object is defined with the { } notation : let spaceship = {};

Objects are used to store data thanks to key/value pairs (called properties). Properties can be anything (string, number, functions etc...)

const spaceship = {
  'Fuel Type' : 'diesel', // need ' ' here because there is a space on the property name
  color: 'silver'
};

You then access properties with the .dot operator: spaceship.color >>> will return silver

Or with brackets: spaceship['Fuel Type'] (You have to use brackets actually if the property name has a space, a number or a special character)

If you call a property that doesn't existe, it returns undefined

You can also add, modify or delete properties:

  • spaceship.type = 'alien' > If the property "type" exists, it will be modified. Otherwise it will be created
  • delete spaceship.mission > Will delete "mission" property

When you store a function inside an object, you call it a method:

  • A property is what an object has
  • A method is what an object does

When you add a method to an object, the value of the property is an anonymus function:

const alienShip = {
  invade: function () {
  console.log('Hello! We have come to dominate your planet!');
  }
};

Or shorter like that:

const alienShip = {
  invade () {
  console.log('Hello! We have come to dominate your planet!');
  }
};

To call a method inside an object you also use the .dot operator, with the function name and parenthesis: alienShip.invade();

You can nest objects within objects (pass an object as a parameter of an higher object). And you can then call nested objects with a serie of .dot operators: spaceship.nanoelectronics['back-up'].battery;

Pass by Reference: is the fact of modifiying an object property directly via a function.

First you define your object:

// First you define your object:
const spaceship = {
  'Fuel Type' : 'Turbo Fuel',
  homePlanet : 'Earth'
};

Then you create functions that will modify/add a property to this objec:

const greenEnergy = obj => {
  obj['Fuel Type'] = 'Avocado Oil';
};

const remotelyDisable = obj => {
  obj.disabled = true;
};

Then if you call those functions on your object:

  • greenEnergy(spaceship);
  • remomtelyDisable(spaceship);

It will modify your object like that:

const spaceship = {
  'Fuel Type': 'Avocado Oil',
  homePlanet: 'Earth',
  disabled: true
};

Object loop : You can't use a normal loop on objects (because object key-value pairs are not in order). So you have to use for...in loop.

Ex:
const crew = {
  captain: 'Lily',
  'chief officer': 'Dan',
  medic: 'Clem',
  translator: 'Patrick'
}

for (let crewMember in crew) {
  console.log(`${crewMember}: ${crew[crewMember]}`);
}

Will outputs:

  • "captain: Lily"
  • "chief officer: Dan"
  • "medic: Clem"
  • "translator: Patrick"

"This" KW: when you define a method inside an object an that this function call properties of this same object, you have to use the this kw, otherwise the scope of the function doesn't allows you to access the property:

const robot = {
  model: '1E78V2',
  energyLevel: 100,
  provideInfo() {
    return `I am ${this.model} and my current energy level is ${this.energyLevel}.`
  }
};

console.log(robot.provideInfo());

Without the this kw, the provideInfo() function doesn't work because it can't access model and energyLevel.

WARNING: the this kw doesn't work the same way, with arrow functions. Because arrow function already have an inherent this which is the one of the global scope. So you can't use this with arrow functions inside objects.

Privacy: In JS there is no built-in privacy for objects. So there is a naming convention to signal that a property is not supposed to be altered:

const bankAccount = {
 _amount: 1000
};

Getters: You use the "get" functions inside an object to perform more complex operations inside your object. (It is like a normal method property with a get before function name). It allows you to :

  • Access and perform actions on the data of the object
  • Using conditional if...else inside your object
  • Access properties of the object, using this kw

Tips:

  • get (and set) methods can't have the same name than a property. So a good practice is to put a "_" before the property names
  • When calling a get method, no need to put parenthesis (it is like accessing a property)
Ex:
const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fullName() {
    if (this._firstName && this._lastName) {
      return `${this._firstName} ${this._lastName}`;
    } else {
      return 'Missing a first name or a last name.';
    }
  }
};

// to call the getter method:
person.fullName; // outputs 'John Doe'

Setters Methods work exactly like getter methods but allows you to reassign values of existing properties within an object:

  • getters allows you to "get" (access) values within an object
  • setters allows you to "set" (change, modify, reassign) values within an object
Ex:
const person = {
  _age: 37,
  set age(newAge) {
    if (typeof newAge === 'number') {
      this._age = newAge;
    } else {
      console.log('You must assign a number to age');
    }
  }
};

// to use the setter method:
person.age = 40; // now console.log(person._age); will outputs 40

Factory functions: represent the "structure" of an object. You use it when you want to create a lot of similar objects. You create a factory function that have the « property » structure of the object you want to create. For that you need to create a function that « return » an object. And you pass your object properties as your function arguments. And then by calling this function and passing it arguments, you create a new object.

Ex: this is a factory function to create a monster

const monsterFactory = (name, age, energySource, catchPhrase) => {
  return {
    name: name,
    age: age,
    energySource: energySource,
    scare() {
      console.log(catchPhrase);
    }
  }
};

Then you can create a new monster only by calling the function:

const ghost = monsterFactory('Ghouly', 251, 'ectoplasm', 'BOO!');

ghost.scare(); // outputs 'BOO!'

Since ES6 you have a shortcut. Because the key and the value of the proprerties where always the same, you can simplify and write only the key, like that:

const monsterFactory = (name, age) => {
  return {
    name,
    age
  }
};

Destructured assignment: is a shortcut that allows you to extract the property of an object to put it in a variable. Normally you would do like that:

const vampire = {
   name: 'Dracula',
   residence: 'Transilvania',
   preference: {
    day: 'stay inside',
    night: 'satisfy appetite'
  }
};

const residence = vampire.residence;
console.log(residence); // will print 'Transilvania'

But to save you some keystrokes you can put the name of the property inside {} directly as the name of the variable like that:

const {residence} = vampire;
console.log(residence); // will print 'Transilvania'

Common built-in object methods:

  • Object.keys(my_object) : will return an array with all the keys of my_object
  • Object.values(my_object) : will return an array with all the values of my_object
  • Object.entries(my_object) : will return an array of arrays with all the entries of my_object. Each nested array will have the key as first element and the value as second element
  • Object.assign(new_properties, my_object) : will return a new object that have the same properties of my_object + the new properties. (Or will modify those properties if they were already in my_object

classes

Classes are tool to quickly produce similar objects. Classes are like "templates" used to create objects.

For example if you have this object:

let halley = {
  _name: 'Halley',
  _behavior: 0,

  get name() {
    return this._name;
  },

  get behavior() {
    return this._behavior;
  },

  incrementBehavior() {
    this._behavior++;
  }
};

You can create this class to build the same type of objects:

class Dog {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }

  get name() {
    return this._name;
  }

  get behavior() {
    return this._behavior;
  }

  incrementBehavior() {
    this._behavior++;
  }
};

And then you can create new objects thanks to the class. It is called "create an instance". Like that:

const halley = new Dog('Halley');

// It will create this object:
Dog {
_name: 'Halley',
_behavior: 0
}

Details of how to build a class:

  • Naming convention: in JS Classes are upper CamelCase: MyClass
  • You need a constructor method. You will invoke it every time you create a new instance of the class
  • Inside the constructor() method you use the this kw (to refers to an instance of this class)
  • You can't include comas between your different methods (only synthax difference with an object)
  • You need to use the new kw to create an instance of a class

Inheritance: when you have several similar classes you can create a "parent" class that will share the same properties and methods that its children classes. The children classes will have the same properties and methods but you can also add new ones.

So if we take our last example (dog creation). We can actually create a parent Animal class, like that:

class Animal {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }

  get name() {
    return this._name;
  }

  get behavior() {
    return this._behavior;
  }

  incrementBehavior() {
    this._behavior++;
  }
};

And after that create a child Cat class, that will have the same name and behavior properties, but also a new one called usesLitter :

class Cat extends Animal {
  constructor(name, usesLitter) {
    super(name);
    this._usesLitter = usesLitter;
  }
};

So to create a child class you need:

  • The extend kw. That makes the parent class available inside the child class
  • The super kw. That calls the constructor of the parent class. You need it for the property that have an argument (To be able to pass this argument to the child class). Otherwise you don't need to even define the property, each child class automatically have the properties (and methods) of its parents. You always have to call the super properties before the ones specific to the child class. (Always use the super kw before the this kw)

Tips: if you have several properties with arguments that need to be called in the parent class by the super KW, you all put it in the SAME super kw (not several ones). Like that:

class Primary extends School {
  constructor(name, numberOfStudents, pickupPolicy) {
    super(name, 'primary', numberOfStudents);
    this._pickupPolicy = pickupPolicy;
  }
};

Static Methods: Sometimes you will want a class to have methods that aren't available in individual instances, but that you can call directly on the class itself. (They are similar to class methods in Ruby)

To create a static method you need to use the static kw like that:

class Animal {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }

  static generateName() {
    const names = ['Angel', 'Spike', 'Buffy', 'Willow', 'Tara'];
    const randomNumber = Math.floor(Math.random()*5);
    return names[randomNumber];
  }
};

We create a static method that return a random name when called. Because of the static kw we can only access this method by appending it to the Animal class.

So you can call the generateName method like that : Animal.generateName()

But if you create instances of Animal (or child classes), for example : const tyson = new Animal('Tyson')

Then you can't do that : tyson.generateName() >> It will return TypeError

modules

In JS, Modules are reusable pieces of code that can be exported from one program and imported for use in another program.

The old way to do it is:

  • Export your code with the module.exports statement
  • Import your code with the require('file_path') function
// export:
let Airplane = {};
Airplane.myAirplane = "StarJet";

module.exports = Airplane;

// import:
const Airplane = require('./1-airplane.js');

function displayAirplane() {
  console.log(Airplane.myAirplane);
}

displayAirplane();

>>> You imported your module and assigned it to a local variable. You then can use all the methods and properties of your module within your file.

You can also wrap your data within the module.exports statement to directly export it:

module.exports = {
  myAirplane: 'CloudJest';
};

The new way (ES6) to do it is with export & import kws.

Export Default: if you want to export your entire code into one module:

// export:
let Menu = {};

export default Menu;

// import (notice you don't need curly brackets):
import Menu from './menu';
  • You can have only one export default per file
  • The export name must be the name of the object you export (can be anything: a method, an object, a variable etc...)
  • The import name (Menu here) specifies the name of the variable to store the default export in (this name is chosen and can be anything). It is the name you will use to execute your module code
  • After the from kw you put the path of the file

Named Exports and Imports: in ES6 the export and import kw also allows you to export data through the use of variables. You can create variables, methods, objects or any data and export it with its specific name to be stored in its own variable:

// export
let specialty = '';
function isVegetarian() {
};

export { speciality, isVegetarian } from './menu';

// import (notice you do need curly brackets):
import { speciality, isVegetarian } from './menu';

You can also write the export kw before your variable/function to export it directly:

export let specialty = '';
export function isVegetarian() {
};

Export as & import as: You can use the export as kw to change the name of the variable you want to export:

let specialty = '';
function isVegetarian() {
};
function isLowSodium() {
};

export { speciality as chefsSpecial, isVegetarian as is Veg, isLowSodium };

After that you have to import it normally with the alias name you created

You can also import as to give an alias or change the alias of a variable when you import it

Import all: you can import an entire module and give it a specific alias. It will store it in an object that you can use later on

// export
let specialty = '';
function isVegetarian() {
};
function isLowSodium() {
};

export { speciality as chefsSpecial, isVegetarian as is Veg, isLowSodium };

// import
import * as Menu from './menu';

Menu.chefsSpecial;
Menu.isVeg();
Menu.isLowSodium();

You can also combine and mix every export types together (default exports, named exports, direct on declaration export etc…):

// export
let specialty = '';
function isVegetarian() {
};
export function isLowSodium() {
};
function isGlutenFree() {
};

export { speciality as chefsSpecial, isVegetarian as is Veg };
export default isGlutenFree;

// import
import { speciality, isVegetarian, isLowSodium } from './menu';

import GlutenFree from './menu';

promises

Here an example of a promise construction:

const executorFunction = (resolve, reject) => {
  if (someCondition) {
      resolve('I resolved!');
  } else {
      reject('I rejected!');
  }
}
const myFirstPromise = new Promise(executorFunction);
  • We declare a variable: myFirstPromise that will store our promise
  • To create a new promise you use the promise constructor method Promise() with the new kw
  • executorFunction() is passed to the constructor method and has two functions as parameters: resolve and reject
  • If someCondition evaluates to true, we invoke resolve()
  • If not we invoke reject()
  • (Promises are objects)

The executor function generally starts an asynchronous operation and dictates how the promise should be settled. So normally the resolve() and reject() functions aren't defined by the programmer. When the Promise constructor runs, JavaScript will pass its own resolve() and reject() functions into the executor function.

Promise states: a promise is in one of these 3 states:

  • pending: when it is not settled yet
  • fullfiled: when it settled and the operation was completed successfully (the resolve() function is executed)
  • rejected: when it settled and the operation failed (the reject() function is executed)

Asynchronicity example using setTimeout(): setTimeout() is a functions used to execute some code after a delay

const delayedHello = () => {
  console.log('Hi! This is an asynchronous greeting!');
};

setTimeout(delayedHello, 2000);

// delayedHello() will be invoked but with a delay of 2 seconds.

Asynchronous JavaScript uses something called the event-loop. After two seconds, delayedHello() is added to a line of code waiting to be run. Before it can run, any synchronous code from the program will run. Next, any asynchronous code in front of it in the line will run. (This means it might be more than two seconds before delayedHello() is actually executed).

Ex: of how you will use setTimeout() to construct asynchronous promise

const returnPromiseFunction = () => {
  return new Promise((resolve, reject) => {
    setTimeout(( ) => {resolve('I resolved!')}, 1000);
  });
};

const prom = returnPromiseFunction();

>>> We invoked returnPromiseFunction() wich return a promise. We assign that promise to the variable prom. prom will initially have a status of "pending". (it hasn't settled yet). Then after 1 second it will settle and be "fulfilled", and resolve with the message "I resolved!".

Then() method: asynchronous promise always have an initial state of pending and then will settle. We use the then() method to say to the program what after it settled: "I have a promise, here's what I want to happen when it settle". So then() will be fired only after the status of the promise has been settled (fulfilled or rejected).

then() is a higher-order function, it takes two callback functions as arguments. We refer to these callbacks as handlers:

  • The first argument (the first handler) is called onFulfilled, it is a success handler. It contains what to do when the promise fulfills
  • The second argument (the second handler) is called onReject, it is a failure handler. It contains what to do when the promse rejects

We can invoke then() with one, both or zero handlers

Ex:
const prom = new Promise((resolve, reject) => {
  resolve('Yay!');
});

const handleSuccess = (resolvedValue) => {
  console.log(resolvedValue);
};

prom.then(handleSuccess); // outputs 'Yay!'

>>> Since prom resolves, handleSuccess() is invoked with prom‘s resolved value, 'Yay', so 'Yay' is logged to the console.

But with typical promise, we won't know whether it will fulfill or reject, so we need to provide the logic for either case (the success handler and the failure handler):

let prom = new Promise((resolve, reject) => {
  let num = Math.random();
  if (num < .5 ){
    resolve('Yay!');
  } else {
    reject('Ohhh noooo!');
  }
});

const handleSuccess = (resolvedValue) => {
  console.log(resolvedValue);
};

const handleFailure = (rejectionReason) => {
  console.log(rejectionReason);
};

prom.then(handleSuccess, handleFailure);

catch() method: to write cleaner code we use the separation of concerns principle. So we separate our resolved logic from our rejected logic:

prom
  .then((resolvedValue) => {
    console.log(resolvedValue);
  })
  .then(null, (rejectionReason) => {
    console.log(rejectionReason);
  });

And to be even cleaner we use the catch() function. It takes only one argument: the failure handler. It is like using a then() with only the failure handler:

prom
  .then((resolvedValue) => {
    console.log(resolvedValue);
  })
  .catch((rejectionReason) => {
    console.log(rejectionReason);
  });

Chaining multiple promises: it is called composition:

firstPromiseFunction()
.then((firstResolveVal) => {
  return secondPromiseFunction(firstResolveVal);
})
.then((secondResolveVal) => {
  console.log(secondResolveVal);
});
  • We invoke firstPromiseFunction() witch returns a promise
  • We invoke then() with an anonymous function as the success handler
  • Inside the success handler we return a new promise: secondPromiseFunction() with the first promise's resolved value as a parameter. (Probably that this second promise needed the result of the first promise to run. That is why we needed to wait for the first promise to settle before runing it)
  • We invoke a second then() to handle the logic of the second promise
  • Inside that second then() we have a success handler which will log the second promise's resolved value to the console.

If you want to handle failure, you just put a catch() method chained after the last then(). It will be executed if any of the promise reject. (But you won't know which one...)

2 common mistakes:

  • Nesting promises instead of chaining them: puting the 2nd then() inside the success handler of the first promise
  • Forgetting to return a promise. If we don't return anything inside the first then(), the second then() will just handle the logic of the first promise. (It is like if we didn't have the first then())

Promise.all() : is a way to handle multiple promise without caring about the order. Promise.all() accepts an array of promises as its argument and returns a single promise. That single promise will settle in one of two ways:

  • If every promise in the argument fulfills > Promise.all() will resolve with an array containing the resolved value from each promise in the argument array
  • If any promise from the argument array rejects > Promise.all() will immediately reject with the reject value of the promise that rejected
let myPromises = Promise.all([returnsPromOne(), returnsPromTwo(), returnsPromThree()]);

myPromises
  .then((arrayOfValues) => {
    console.log(arrayOfValues);
  })
  .catch((rejectionReason) => {
    console.log(rejectionReason);
  });

To sum up about promises:

  • Promises are JavaScript objects that represent the eventual result of an asynchronous operation
  • Promises can be in one of three states: pending, fulfilled, or rejected
  • A promise is settled if it is either fulfilled or rejected
  • We construct a promise by using the new keyword and passing an executor function to the promise constructor method (new Promise(executorFunction))
  • We use then() with a success handler callback containing the logic for what should happen if a promise fulfills
  • We use catch() with a failure handler callback containing the logic for what should happen if a promise rejects
  • Promise composition enables us to write complex, asynchronous code that is still readable. We do this by chaining multiple then() and catch()
  • To use promise composition correctly, we have to remember to return promises constructed within a then()
  • We should chain multiple promises rather than nesting them
  • We can handle multiple promises at the same time with the Promise.all() method

async await

Since ES8, async…await was introduced and allows a better readability and scalability of promises. async kw is used to write function that handle asynchronous actions. async kw allows to creates functions that return a Promise:

async function myFunc() {
  // Function body here
};

myFunc();

// or with arrow function:
const myFunc = async () => {
  // Function body here
};

myFunc();

Async functions always return a promise. It will return in 3 possible ways:

  • If there is nothing returned, it will return a promise with a fulfilled value of undefined
  • If there is a non-promise value returned, it will return a promise fulfilled to that value
  • If a promise is returned it will simply return that promise

So, a normal promise written like that:

function asyncFuncExample(num){
  return new Promise((resolve, reject) => {
    if (num === 0){
      resolve('zero');
    } else {
      resolve('not zero');
    }
  })
}

Can be written with the async kw like that:

async function asyncFuncExample(num) {
  if (num === 0) {
    return 'zero';
  } else {
    return 'not zero';
  }
};

Await: Without the await kw, an async function don't do much. The await kw can only be used inside an async function. It returns the resolved value of a promise. await will pauses the execution of the async function until the desired promise is settled.

Ex:
async function asyncFuncExample(){
  let resolvedValue = await myPromise();
  console.log(resolvedValue);
}

asyncFuncExample(); // outputs: I am resolved now!
  • Inside asyncFuncExample() we use await to pause our execution until myPromise() is settled
  • We moreover assign myPromise() resolved value to the variable resolvedValue
  • Then we log resolvedValue to the console
  • (The resolvedValue of myPromise is "I am resolved now!")

It is particulary usefull when you do dependent promises (a serie of asynchronous action).

Indeed, with native promises you will do a chain of then() functions. Like that:

function nativePromiseVersion() {
  returnsFirstPromise()
  .then((firstValue) => {
    console.log(firstValue);
    return returnsSecondPromise(firstValue);
  })
  .then((secondValue) => {
    console.log(secondValue);
  });
}

Whereas with async…wait:

async function asyncAwaitVersion() {
  let firstValue = await returnsFirstPromise();
  console.log(firstValue);
  let secondValue = await returnsSecondPromise(firstValue);
  console.log(secondValue);
}

Try...Catch: When catch() is used with a long promise chain, there is no indication of where in the chain the error was thrown. This make debugging challenging.

With async…await, we use the try…catch statements for error handling:

async function usingTryCatch() {
  try {
    let resolveValue = await asyncFunction('thing that will fail');
    let secondValue = await secondAsyncFunction(resolveValue);
  } catch (err) {
    // Catches any errors in the try block
    console.log(err);
  }
}

usingTryCatch();

Here we will "try" what is in the body of the try kw. If any of the promised will reject, the catch body will be executed, with the err argument being the reject value of the promise that was rejected.

Handling Independent Promises: If we have several independent promises that we need to await there is a trick. Indeed, we don't want to wait promise1 to be solved, then promise2 to be solved, and then do our resolution…

We want both promises to run (at the same time) and then, when both are solved, we want our resolution. We can do it like that:

async function concurrent() {
  const firstPromise = firstAsyncThing();
  const secondPromise = secondAsyncThing();
  console.log(await firstPromise, await secondPromise);
}

Await Promise.all() : We can also use the await Promise.all() to wait for multiple promises. Indeed Promise.all() takes an array of promises as argument and will resolve when all of these promise are fulfilled. So if we put await before it, it will await all those promises to be settled before going further.

async function asyncPromAll() {
  const resultArray = await Promise.all([asyncTask1(), asyncTask2(), asyncTask3(), asyncTask4()]);
  for (let i = 0; i < resultArray.length; i++){
    console.log(resultArray[i]);
  }
}

http requests

One of JavaScript's greatest assets is its non-blocking properties, or that it is an asynchronous language. It uses an event loop to handle asynchronous function calls: when a program is run, function calls are made and added to a stack. The functions that make requests that need to wait for servers to respond get sent to a separate queue. Once the stack has cleared, then the functions in the queue are executed.

Ex:
console.log('First message!');
setTimeout(() => {
   console.log('This message will always run last...');
}, 0);
console.log('Second message!');

The first and second message are run from the stack. The one in setTimeout() is added to the queue so it will always run after the others. (Even if the setTimeout() is set to 0 milliseconds.

XHR Get Request: the AJAX GET request allows you to retrieve data from a server.

Boilerplate of an AJAX GET request using an XMLHttpRequest:

const xhr = new XMLHttpRequest();
const url = 'https://api-to-call.com/endpoint'

xhr.responseType = 'json';
xhr.onreadystatechange = () => {
  if (xhr.readyState === XMLHttpRequest.DONE) {
  return xhr.response;
  }
}

xhr.open('GET', url)
xhr.send()

XHR Post Request: The AJAX POST request allows you to send data to a server.

Boilerplate of an AJAX POST request using an XMLHttpRequest:

const xhr = new XMLHttpRequest();
const url = 'https://api-to-call.com/endpoint'

xhr.responseType = 'json';
xhr.onreadystatechange = () => {
  if (xhr.readyState === XMLHttpRequest.DONE) {
  return xhr.response;
  }
}

xhr.open('GET', url)
xhr.send()

Fetch Function:

  • It takes an URL (your API endpoint) as the first argument.
  • It has an optional object as second argument. In this object you can specify options for your request:
    • the method of your request ('GET', 'POST' etc...)
    • the headers of your request ({ "Authorization": "my-api-key" } for example)
    • the body of your request (for a POST request for example)
    • etc...
  • It creates a request object that contains relevant information that an API needs
  • It sends that request object to the API endpoint provided
  • It returns a promise that ultimately resolves to a response object, which contains the status of the promise with information the API sent back

Boilerplate of a simple fetch GET request:

fetch('https://api-to-call.com/endpoint')
  .then((response => response.json)
  .then((data) => {
    // Do something with the data
  });

Boilerplate of a complete fetch GET request:

fetch('https://api-to-call.com/endpoint').then((response) => {
  if (response.ok) {
    return response.json();
  }
  throw new Error('Request failed!');
},
  (networkError) => {
  console.log(networkError.message);
})
.then((jsonResponse) => {
  return jsonResponse
});
  • You call the fetch() function and pass the URL of your API request as an argument
  • You chain a then() method with an anonymous arrow function as its first argument (which is the "success handler")
  • This success callback function takes one parameter response
  • Inside the response callback function you check if the ok property of response (response.ok) is truly (inside a if statement)
  • If it is truly you return response.json()
  • Else, you will throw a new error message if response.ok is falsy
  • You add your second argument (which is the "failure hander") to your then() function. It will be a callback function that takes a single parameter networkError and log networkError.message to the console
  • You chain another then() method (that will be executed only if the first one has finished and doesn't throw an error)
  • You pass it a callback function that take jsonResponse as its parameter and return jsonResponse

Boilerplate of a simple fetch POST request:

fetch('https://api-to-call.com/endpoint', {
  method: 'POST',
  body: JSON.stringify({ id: '200' })
}).then((response => response.json)
  .then((data) => {
    // Do something with the data
  });

Boilerplate of a complete fetch POST request:

fetch('https://api-to-call.com/endpoint', {
  method: 'POST',
  body: JSON.stringify({id: '200'})
}).then((response) => {
  if(response.ok) {
    return response.json();
  }
  throw new Error('Request failed!');
},
(networkError) => {
  console.log(networkError.message);
})
.then((jsonResponse) => {
  return jsonResponse;
});

It is similar to the GET request, only that the initial call takes two arguments: an endpoint and an object, instead of just one. In this object you have the verb and the body of the HTTP request. The body is the info you want to send to the server.

Fetch with Async and await KW: you can also use async, await, try and catch kw with fetch() to use GET and POST XHR requests:

GET:

const getData = async () => {
  try {
    const response = await fetch('https://api-to-call.com/endpoint');
    if(response.ok) {
      const jsonResponse = await response.json();
      return jsonResponse
    }
    throw new Error('Request failed!')
  } catch(error) {
    console.log(error);
  }
};

POST:

const getData = async () => {
  try {
    const response = await fetch('https://api-to-call.com/endpoint', {
      method: 'POST',
      body: JSON.stringify({id: 200})
    });
    if(response.ok) {
      const jsonResponse = await response.json();
      return jsonResponse
    }
    throw new Error('Request failed!')
  } catch(error) {
    console.log(error);
  }

DOM

DOM: the DOM is the Document Object Model, it is the browser representation of the HTML file. (Basically what you see when you open a website page on your internet browser...). Javascript allows you to interact with the DOM.

Common DOM manipulation methods:

  • document.querySelector('css-selector') : select the first DOM element that respond to the CSS selector. The CSS selector works exactly like in CSS, it can be a class, an ID, a HTML element etc...
  • document.getElementById('my-id') : select the first DOM element that has this id. More performant than #querySelector because will only search for ids (and not classes and other selectors)
  • document.querySelectorAll('css-selecto') : select all DOM elements that respond to the CSS selector, and return it in an Array
  • element.insertAdjacentHTML('beforend', '<p>blablabla</p>') : add HTML content to the DOM element. First argument (ex: 'beforeend') defines where, and second argument defines what
  • element.classList.add('class-name') : add CSS class to the DOM element
  • element.classList.remove('class-name') : remove CSS class to the DOM element
  • element.classList.toggle('class-name') : toggle CSS class to the DOM element (it will add it if it's not here, and remove it if its here)
  • element.style.color = 'blue' : change or add DOM element css property. Can be any CSS property. Actually element.attribute returns the value of any HTML element attribute (style, id, src etc...), and element.attribute = "new value" modifies this attribute
  • element.innerText : returns only the text inside the DOM element (without HTML tags)
  • element.innerHTML : return all the HTML content inside DOM element
  • element.dataset : return all data values of the DOM element in an object. So for example if element is: <div data-key="123" data-first-name='Renodor'></div>, you will have: element.dataset.key == '123, and element.dataset.firstName == 'Renodor' (note that data attributes are converted to lower camelCase)

events

Events: clicks, scrolls, mouse over, select etc... All those interactions are "events" that you can "listen" and react to with Javascript.

Some common events:

  • DOMContentLoaded : fire when DOM is fully loaded
  • click : fire when you click on the element
  • change : fire when input, select or textarea elements value change
  • focus : fire when you focus on the element
  • blur : fire when you loose focus on the element (focusout event also exists)
  • keydown : fire when a key (of the keyboard) is pressed
  • keyup : fire when a key (of the keyboard) is released
  • mouseover : fire when cursor of mouse (or trackpad) goes onto the element
  • resize : fire when window is resized
  • submit : fire when the form is submited
  • scroll : fire when the element (or the DOM) is scrolled
  • touchstart : fire when the element is touched (for tactil screens like mobiles)

Those events can be called on DOM elements (like a div, a form, an input etc...), or directly on the page itself (document, window)

To start listening on an event we use the addEventListener method:

element.addEventListener('click', (event) => {
  // Do something with the event, like get the element that triggered it:
  console.log(event.currentTarget)
})

event.preventDefault() prevent the default behavior of an event. For example, by default when a form is submited the form action will be executed (probably sending you to another page). If you want to prevent that, because you want to modify the form data before sending it, you'll do something like:

form.addEventListener('submit', (event) => {
  // Prevent event default behavior
  event.preventDefault()

  // Now do something with the form data...
})