At a high level, what are some classic elements of modern JavaScript?

Overview

  1. ES6 Modules: import and export syntax
  2. Arrow Functions: const add = (a, b) => a + b;
  3. Destructuring: const { name } = user;
  4. Spread & Rest Operators: const newArray = [...oldArray, newItem];
  5. Promises & Async/Await: Handling async operations
  6. Template Literals: console.log(`Hello ${name}`);
  7. Array Methods (map, filter, reduce): Functional programming concepts

1. ES6 Modules

Exporting

2 Types:

  1. Named Exports
  2. Default Export (only one)

You may export variables, functions, or classes for use in other files once imported. Export multiple using {}.

export default sayGoodmorning = "Good morning!"; // default export
export const age = 30; // variable
export function sayGoodbye() { return; } // function
export const greet = (name) => `Hello, ${name}!`; // ...variable...function?
export { name1, name2, name3 }; // multiple export

Importing

  1. Named Exports must be imported using the name they were exported with and must be inside {}.
  2. Default Export can be renamed if desired.
  3. Alternatively, everything can be imported using * and an alias, use .default to access the default module.
import defaultFuncSillyName, { name1, name2 } from './helpers.js'; // import default AND some named exports
import * as utils from './utilities.js'; // import everything
 
console.log(utils.default(6, 3) + utils.add(3, 4)); // usage of .default()

2. Arrow Functions

Put simply, they 1) are a shortened way of writing functions and 2) automatically bind this from their surrounding context.

function add(a, b) { return a + b; }
// Equivalent to this arrow function
const add = (a, b) => a + b;

Notes:

  • Parameter parenthesis are optional if there is only one parameter
  • If there is only one expression (effect) then the {} and return are optional.

3. Destructuring (Pattern Matching)

Remember Rust? Or Ruby? Or Haskell?

(JSON) Object Destructuring:

const user = { name: 'Alice', age: 25 };
 
const { name, age } = user; // doing this method, the names must match the object
const { name, age, isHungry = false } = user; // example of default for nonexistant property

Array Destructuring:
There’s more to this and it tends to be pretty messy. But! My favorite feature is destructuring (pattern matching) with rest classically found in Haskell and other functional languages.

const colors = ['red', 'green', 'blue']; 
const [first, second] = colors; // destructuring and throwing away rest
const [first, ...rest] = colors; // destructuring and keeping rest
const [fst, snd, thr, frt="purple"] = colors // destructuring and providing default value if missing

4. Spread and Rest Operators (...)

Spread
Copying and merging arrays/objects. Think of the operator as “spreading” the array out into a comma separated list.

const oldArray = [1, 2, 3];
const newArray = [...oldArray, 4, 5]; // [1, 2, 3, 4, 5]
 
const person = { name: 'Alice', age: 25 };
const updatedPerson = { ...person, city: 'NYC' };

Rest
Think of variable arguments in C or Java and how it creates an array you can go through.

function sum(...numbers) {
  return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

5. Promises & Async/Await

But first, a distinction. Asynchronous programming is a single chef in the kitchen that works on other tasks while waiting for the first ones to finish. Threaded programming is two chefs working alongside each other that must combine their output afterwards. JavaScript is single threaded and often requires asynchronous operations for longer operations like API fetching.

By learning the ins and outs of promises, it will be easier to understand await/async.

Promises

Can either be pending, fulfilled (with a value), or rejected (with an error).

// Reference this example
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (/* */) {
        resolve("Data received");
      } else {
        reject("Error: Something went wrong");
      }
    }, 2000);
  });
};

Resolve and Reject
Promises are given an arrow function with two parameters: resolve and reject. These parameters are functions that can be called inside the arrow function, and their value (usually the computed result or an error) will be passed to then and catch during usage (not definition) for further handling. Observe how we are able to use the value passed to resolve in the following snippet.

const myPromise = new Promise((resolve, reject) => {
	resolve("Success!"); // Resolving with a string
});
 
myPromise.then(value => {
    console.log(value); // Output: "Success!"
});

Timeouts
A timeout is optionally often used to delay execution or cancel the request in the event that it takes too long. Look into setTimeout with AbortController and signal to abort a request.

Async/Await

async is used to declare an asynchronous function that always returns a value wrapped in a promise. await is used inside of an async function to pause execution until a promise resolves.

async function fetchUser() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
    const user = await response.json();
    console.log("User:", user);
  } catch (error) {
    console.error("Error:", error);
  }
}
 
fetchUser();

At a high level, async/await is built on top of promises with code that sometimes looks cleaner and more like synchronous code. And so, it is not unreasonable to use async/await in most scenarios (except parallel execution, or so I’ve heard).

This topic could potentially use some expanding. TODO

6. Template Literals

Comparable to printf in many languages. Defined using backticks. Side note: single quotes vs double quotes are identical in JavaScript.

// Insert variables/expressions using `${expr}`
const result = `The sum of ${a} and ${b} is ${a + b}.`;
 
// Multiline strings
const paragraph = `Lorem
				ipsum dolor`

7. Higher Order Functions

Classic functional programming. Notice the form of the arrow functions.

Map

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2); // [2, 4, 6]

Filter

const ages = [18, 25, 30, 15];
const adults = ages.filter(age => age >= 18); // [18, 25, 30]

Reduce
Here it is important that the first parameter of the arrow function is the accumulator and the second is the name given to the current element. The second parameter of the reduce function is the initial value (technically optional).

const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, num) => acc + num, 0); // 10