NodeJS by Example: Modern JS

This section is a crash course on the basic concepts of modern JavaScript that you'll see both in NodeJS and the browser.

Variables are used to store data that can be used later in the program. In JavaScript, you can declare a variable using the `let` or `const` keywords

const myImmutableVariable = 42;

let myMutableVariable = 42;
myMutableVariable = 43; // reassignable

Primitives refer to fundamental, immutable data types that are not objects. These include:

Strings: A sequence of characters.

const name = 'NodeJSByExample';
console.log(typeof name); // string

Numbers: Represents numeric values, both integers and floating-point numbers.

const age = 30;
console.log(typeof age); // number

BigInt: Represents large integers that cannot be represented by the number type.

const bigInt = 9007199254740991n;
console.log(typeof bigInt); // bigint

Booleans: Represents a logical value, either true or false.

const isNode = true;
console.log(typeof isNode); // boolean

Undefined: Represents a variable that has been declared but not assigned a value.

const undefinedValue = undefined;
console.log(typeof undefinedValue); // undefined
let value;
console.log(typeof value); // undefined

Null: Represents an intentional absence of any object value.

const nullValue = null;
console.log(typeof nullValue); // object
console.log(nullValue === null); // true

Symbols: Represents a unique and immutable value that may be used as an object property key.

const symbol = Symbol('description');
console.log(typeof symbol); // symbol

Functions are blocks of code that can be called to perform a specific task. In JavaScript, functions are first-class objects, which means they can be passed around like any other value.

function helloWorld() {
  console.log("Hello, World!");
}

Arrow functions, a concise syntax for writing functions:

const add = (a, b) => a + b;
console.log(add(2, 3)); // 5

IIFE (Immediately Invoked Function Expression) is a function that is executed immediately after it is created.

(function() {
  console.log("I am IIFE");
})(); // IIFE

Template Literals, string interpolation using backticks:

const name = "Matt";
console.log(`Hello, ${name}!`);

Destructuring is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

const [a, b] = [1, 2];
const { x, y } = { x: 10, y: 20 };

Default parameters setting default values in functions:

function greet(name = "Guest") {
  return `Hello, ${name}`;
}

Rest and Spread Operators are two new operators introduced in ES6 that can be used to manipulate arrays and objects.

function printNumbers(...numbers) {
  for(const num of numbers) {
    console.log(num);
  }
}
printNumbers(1, 2, 3, 4, 5); // 1 2 3 4 5

const arr = [1, 2, 3];
const newArr = [...arr, 4, 5]; //  [1, 2, 3, 4, 5]


Modularized code using import and export:

/*math.js*/
export function add(a, b) {
  return a + b;
}

/*main.js*/
import { add } from './math.js';
add(2, 3); // 5

Classes are a template for creating objects, providing initial values for properties and methods.

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hi, I'm ${this.name}`);
  }
}

Callbacks are functions that are passed as arguments to other functions and are executed after some operation has been completed.

function fetchData(callback) {
  setTimeout(() => {
    callback("Data received!");
  }, 2000);
}

fetchData((data) => {
  console.log(data); // Data received!
});

Promises are objects representing the eventual completion or failure of an asynchronous operation. They are used to handle asynchronous operations in JavaScript.

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data received!");
    }, 2000);
  });
}

fetchData().then((data) => {
  console.log(data); // Data received!
});

Async/Await is a new way to write asynchronous code in JavaScript. It makes asynchronous code look and behave more like synchronous code.

async function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data received!");
    }, 2000);
  });
}

async function main() {
  const data = await fetchData();
  console.log(data); // Data received!
}

Promise.all is a method that takes an array of promises and returns a single promise that resolves when all of the promises in the array have resolved.

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Data 1");
  }, 2000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Data 2");
  }, 1000);
});

Promise.all([promise1, promise2]).then((values) => {
  console.log(values); // ["Data 1", "Data 2"]
});

Promise.allSettled is a method that takes an array of promises and returns a single promise that resolves when all of the promises in the array have settled (either resolved or rejected).

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Data 3");
  }, 2000);
});

const promise4 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Error");
  }, 1000);
});

Promise.allSettled([promise3, promise4]).then((results) => {
  console.log(results); // [{ status: "fulfilled", value: "Data 3" }, { status: "rejected", reason: "Error" }]
});

Optional Chaining is a JavaScript feature that allows you to safely access nested properties of an object without worrying about whether the property exists or not.

const person = {
  name: "Alice",
};

console.log(person.job?.title); // undefined

Nullish coalescing is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.

const name = null;
const defaultName = "Guest";

console.log(name ?? defaultName); // Guest

Dynamic Imports allow you to import modules only when they are needed. This can help reduce the initial load time of your application.

async function loadModule() {
  if(condition) {
    const module = await import('./module.js');
    module.someFunction();
    return module;
  } else {
    return import('./another-module.js');
  }
}