bugl
bugl
HomeLearnPatternsPathsSearch
HomeLearnPatternsPathsSearch

Loading lesson path

Learn/Node.js/JS & TS Features
Node.js•JS & TS Features

Node.js ES6+ Features

Flash cards

Review the key moves

1/4
Core idea

What is the main idea behind Node.js ES6+ Features?

Lesson checks

Practice each idea before moving on

Short Mimo-style checks built from this lesson's code, terms, and sequence.

1Quick choice

Which statement best captures the main point of this lesson?

2Fill blank

Complete the missing token from the example code.

// Using ___ (can be changed)
3Order

Put the learning moves in the order that makes the concept easiest to apply.

Promises and Async/Await
Default Parameters
Spread and Rest Operators

What is ES6+?

ES6 (ECMAScript 2015) and later versions add powerful new features to JavaScript that make your code more expressive, concise, and safer.

Node.js has excellent support for modern JavaScript features.

Node.js Compatibility: All modern versions of Node.js (10+) have excellent support for ES6+ features.

Newer versions support even more recent JavaScript additions from ES2020 and beyond.

These modern JavaScript features help you

  • Write cleaner, more readable code
  • Avoid common JavaScript pitfalls
  • Create more maintainable applications
  • Reduce the need for external libraries

let and const

The let and const keywords replaced var as the preferred way to declare variables:

  • let allows you to declare variables that can be reassigned
  • const declares variables that cannot be reassigned (but object properties can still be modified)
  • Both are block-scoped, unlike var which is function-scoped

Example: let and const

// Using let (can be changed)
let score = 10;
score = 20;
// Using const (cannot be reassigned)
const MAX_USERS = 100;
// Block scope with let
if (true) {
  let message = 'Hello';
  console.log(message); // Works here
}

Arrow Functions

Arrow functions provide a concise syntax for writing functions and automatically bind this to the surrounding context.

Key benefits of arrow functions

  • Shorter syntax for simple functions
  • Implicit return for one-line expressions
  • Lexical this binding (arrow functions don't create their own this context)

Example: Arrow Functions

// Traditional function function add(a, b) {
return a + b;
}
// Arrow function (same as above)
const addArrow = (a, b) => a + b;
// Single parameter (no parentheses needed)
const double = num => num * 2;
// No parameters (parentheses needed)
const sayHello = () => 'Hello!';
// Using with array methods
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled);

When NOT to use arrow functions

  • Object methods (where you need this to reference the object)
  • Constructor functions (arrow functions can't be used with new)
  • Event handlers where this should refer to the element

Template Literals

Template literals (template strings) provide an elegant way to create strings with embedded expressions using backticks ( ` ).

Key features of template literals

  • String interpolation with ${expression} syntax
  • Multi-line strings without escape characters
  • Tagged templates for advanced string processing

Example: Template Literals

// Basic string interpolation
const name = 'Alice';
console.log(`Hello, ${name}!`);
// Multi-line string
const message = `
This is a multi-line
string in JavaScript.
`;
console.log(message);
// Simple expression
const price = 10;
const tax = 0.2;
console.log(`Total: $${price * (1 + tax)}`);

Destructuring

Destructuring allows you to extract values from arrays or properties from objects into distinct variables with a concise syntax.

Key features of destructuring

  • Extract multiple values in a single statement
  • Assign default values to extracted properties
  • Rename properties during extraction
  • Skip elements in arrays
  • Extract deeply nested properties

Example: Object Destructuring

// Basic object destructuring
const user = { name: 'Alice', age: 30, location: 'New York' };
const { name, age } = user;
console.log(name, age);

Example: Array Destructuring

// Basic array destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first, second, third);
// Skipping elements
const [primary, , tertiary] = colors;
console.log(primary, tertiary);

Spread and Rest Operators

The spread and rest operators (both written as ... ) allow you to work with multiple elements more efficiently.

  • Spread operator : Expands iterables (arrays, objects, strings) into individual elements
  • Rest operator : Collects multiple elements into a single array or object

Example: Spread Operator

// Array spread - combining arrays
const numbers = [1, 2, 3];
const moreNumbers = [4, 5, 6];
const combined = [...numbers, ...moreNumbers];
console.log(combined);
// Array spread - converting string to array of characters
const chars = [...'hello'];
console.log(chars);

Example: Rest Operator

// Rest parameter in functions function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5));

Default Parameters

ES6+ allows you to specify default values for function parameters, eliminating the need for manual parameter checking in many cases.

Key benefits of default parameters

  • Cleaner function definitions without manual checking
  • More explicit function signatures
  • Default values are only used when parameters are undefined or not provided
  • Default values can be expressions, not just simple values

Example: Default Parameters

// Basic default parameter function greet(name = 'Guest') {
return `Hello, ${name}!`;
}
console.log(greet());
console.log(greet('Kai'));

Classes

ES6 introduced class syntax to JavaScript, providing a clearer and more concise way to create objects and implement inheritance.

Under the hood, JavaScript classes are still based on prototypes.

Key features of JavaScript classes

  • Cleaner syntax for creating constructor functions and methods
  • Built-in support for inheritance using extends
  • Static methods attached to the class, not instances
  • Getter and setter methods for more controlled property access
  • Private fields for better encapsulation (ES2022+)

Example: Class Basics

// Simple class with constructor and method class Person { constructor(name, age) { this.name = name; this.age = age;
}
greet() {
  return `Hello, I'm ${this.name}!`; } } // Create an instance const person = new Person('Alice', 25); console.log(person.greet()); // Hello, I'm Alice!

Example: Class Inheritance

// Parent class class Animal { constructor(name) { this.name = name;
}
speak() {
  return `${this.name} makes a sound.`;
}
}
// Child class class Dog extends Animal { speak() {
return `${this.name} barks!`;
}
}
const dog = new Dog('Rex');
console.log(dog.speak());

Example: Private Class Fields (ES2022+)

// Class with private field (# prefix)
class Counter {
 #count = 0; // Private field
 increment() {
 this.#count++;
 }
 getCount() {
 return this.#count;
 }
}
const counter = new Counter();
counter.increment();
console.log(counter.getCount());
// console.log(counter.#count); // Error: Private field

Promises and Async/Await

Modern JavaScript provides powerful tools for handling asynchronous operations, making it much easier to work with code that involves delays, API calls, or I/O operations.

Promises

Promises represent values that may not be available yet.

They provide a more elegant way to handle asynchronous operations compared to callbacks.

A Promise is in one of these states:

  • Pending : Initial state, neither fulfilled nor rejected
  • Fulfilled : Operation completed successfully
  • Rejected : Operation failed

Example: Basic Promises

// Creating a promise
const fetchData = () => {
 return new Promise((resolve, reject) => {
 // Simulating an API call
 setTimeout(() => {
 const data = { id: 1, name: 'Product' };
 const success = true;
 if (success) {
 resolve(data); // Fulfilled with data
 } else {
 reject(new Error('Failed to fetch data')); // Rejected with error
 }
 }, 1000);
});
};
// Using a promise
console.log('Fetching data...');
fetchData()
.then(data => {
 console.log('Data received:', data);
 return data.id; // Return value is passed to the next .then()
})
.then(id => {
 console.log('Processing ID:', id);
})
.catch(error => {
 console.error('Error:', error.message);
})
.finally(() => {
 console.log('Operation completed (success or failure)');
});
console.log('Continuing execution while fetch happens in background');

Async/Await

Async/await (introduced in ES2017) provides a cleaner syntax for working with promises, making asynchronous code look and behave more like synchronous code.

Example: Async/Await

// Function that returns a promise
const fetchUser = (id) => {
 return new Promise((resolve, reject) => {
 setTimeout(() => {
 if (id > 0) {
 resolve({ id, name: `User ${id}` });
 } else {
 reject(new Error('Invalid user ID'));
 }
 }, 1000);
});
};
// Using async/await
async function getUserData(id) {
 try {
 console.log('Fetching user...');
 const user = await fetchUser(id); // Waits for the promise to resolve
 console.log('User data:', user);
 // You can use the result directly
 return `${user.name}'s profile`;
 } catch (error) {
 // Handle errors with try/catch
 console.error('Error fetching user:', error.message);
 return 'Guest profile';
}
}
// Async functions always return promises
console.log('Starting...');
getUserData(1)
.then(result => console.log('Result:', result))
.catch(error => console.error('Unexpected error:', error));
console.log('This runs before getUserData completes');

Common Async/Await Mistakes

  • Forgetting that async functions always return promises
  • Not handling errors with try/catch
  • Running operations sequentially when they could run in parallel
  • Using await outside of an async function
  • Awaiting non-promise values (unnecessary but harmless)

ES Modules

ES Modules (ESM) provide a standardized way to organize and share code. They were introduced in ES2015 and are now supported natively in Node.js.

Key benefits of ES Modules

  • Static module structure (imports are analyzed at compile time)
  • Default and named exports/imports
  • Better dependency management
  • Tree-shaking (eliminating unused code)

Example: ES Modules

File: math.js

// Named exports
export const PI = 3.14159;
export function add(a, b) {
 return a + b;
}
export function multiply(a, b) {
 return a * b;
}
// Default export
export default class Calculator {
 add(a, b) {
 return a + b;
 }
 subtract(a, b) {
 return a - b;
 }
}

File: app.js

// Import default export
import Calculator from './math.js';
// Import named exports
import { PI, add, multiply } from './math.js';
// Import with alias
import { add as mathAdd } from './math.js';
// Import all exports as a namespace
import * as MathUtils from './math.js';
const calc = new Calculator();
console.log(calc.subtract(10, 5)); // 5
console.log(add(2, 3)); // 5
console.log(mathAdd(4, 5)); // 9
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.multiply(2, 3)); // 6

To use ES Modules in Node.js, you can either:

  • Use the .mjs extension for module files
  • Add "type": "module" to your package.json
  • Use the --experimental-modules flag (older Node.js versions)

The CommonJS module system ( require() and module.exports ) is still widely used in Node.js. ES Modules and CommonJS can coexist in the same project, but they have different semantics.

Enhanced Object Literals

ES6+ introduced several improvements to object literals that make object creation more concise and expressive.

Example: Enhanced Object Literals

// Property shorthand
const name = 'Alice';
const age = 30;
// Instead of {name: name, age: age}
const person = { name, age };
console.log(person);
// Method shorthand
const calculator = {
  // Instead of add: function(a, b) { ... } add(a, b) {
  return a + b;
}, subtract(a, b) {
return a - b;
}
};
console.log(calculator.add(5, 3));

Optional Chaining and Nullish Coalescing

Modern JavaScript introduces syntax to safely access nested properties and provide fallback values.

Optional Chaining (?.)

Optional chaining lets you access deeply nested object properties without worrying about null or undefined values in the chain.

Example: Optional Chaining

function getUserCity(user) {
 return user?.address?.city;
}
const user1 = {
 name: 'Alice',
 address: { city: 'New York', country: 'USA' }
};
const user2 = {
 name: 'Bob'
};
const user3 = null;
console.log(getUserCity(user1)); // 'New York'
console.log(getUserCity(user2)); // undefined
console.log(getUserCity(user3)); // undefined

Nullish Coalescing (??)

The nullish coalescing operator (??) provides a default value when a value is null or undefined (but not for other falsy values like 0 or "").

Example: Nullish Coalescing

function calculatePrice(price, tax) {
 // Only uses default if tax is null or undefined
 return price + (tax ?? 0.1) * price;
}
console.log(calculatePrice(100, 0)); // 100 (correct! tax of 0 was used)
console.log(calculatePrice(100, null)); // 110 (using default)

Modern Asynchronous Patterns

Modern JavaScript provides powerful patterns for handling asynchronous operations. Understanding when to use sequential vs parallel execution can significantly improve your application's performance.

Sequential vs Parallel Execution

  • Sequential: Operations run one after another, each waiting for the previous to complete
  • Parallel: Operations run simultaneously, which is more efficient when operations are independent

Example: Sequential Execution

// Helper function to simulate an API call
function fetchData(id) {
 return new Promise(resolve => {
 setTimeout(() => resolve(`Data for ID ${id}`), 1000);
 });
}
// Sequential execution (~3 seconds total)
async function fetchSequential() {
 console.time('sequential');
 const data1 = await fetchData(1);
 const data2 = await fetchData(2);
 const data3 = await fetchData(3);
 console.timeEnd('sequential');
 return [data1, data2, data3];
}
// Run the sequential example fetchSequential().then(results => { console.log('Sequential results:', results);
});

Next

Node.js Process Management