JavaScript Notes
The programming language of the web
Table of Contents
-
JavaScript Basics
-
Functions and Objects
-
DOM Manipulation
-
Asynchronous JavaScript
Introduction to JavaScript
JavaScript is a programming language that enables interactive web pages and is an essential part of web applications. It's what allows websites to respond to user interactions, update content dynamically, and create a more engaging user experience.
JavaScript Basics
What is JavaScript?
JavaScript is a high-level, interpreted programming language primarily used for creating interactive effects within web browsers. It's one of the core technologies of the World Wide Web, alongside HTML and CSS.
JavaScript allows you to:
- Add interactivity to web pages
- Create dynamic content updates
- Control multimedia
- Animate images
- Build web and mobile applications
Despite its name, JavaScript is not related to Java. It was originally created in 10 days by Brendan Eich while he was working at Netscape in 1995.
// Basic JavaScript example
console.log('Hello, World!');
// Using JavaScript to change an HTML element
document.getElementById('demo').innerHTML = 'Hello JavaScript!';
// Creating a simple function
function greet(name) {
return 'Hello, ' + name + '!';
}
console.log(greet('Learner'));
Variables and Data Types
Variables are containers for storing data values. In JavaScript, you declare variables using var
, let
, or const
:
var
: Function-scoped, can be redeclared and updated (older way, less recommended now)let
: Block-scoped, can be updated but not redeclared within the same scopeconst
: Block-scoped, cannot be updated or redeclared (for constant values)
JavaScript has several data types:
- Primitive types:
String
: Text values ('Hello', "World")Number
: Numeric values (42, 3.14)Boolean
: true or falseundefined
: Variable declared but not assigned a valuenull
: Intentional absence of valueSymbol
: Unique and immutable value (ES6)BigInt
: For large integers (ES2020)
- Reference types:
Object
: Collections of key-value pairsArray
: Ordered listsFunction
: Code blocks that can be executed
// Variable declarations
var oldWay = 'Using var'; // Function-scoped
let modernWay = 'Using let'; // Block-scoped
const CONSTANT = 'Cannot change'; // Cannot be reassigned
// Primitive data types
let name = 'John'; // String
let age = 30; // Number
let isStudent = true; // Boolean
let job = undefined; // Undefined
let salary = null; // Null
// Reference data types
// Object
let person = {
firstName: 'John',
lastName: 'Doe',
age: 30
};
// Array
let colors = ['red', 'green', 'blue'];
// Function
let greet = function() {
return 'Hello!';
};
// Checking data types
console.log(typeof name); // 'string'
console.log(typeof age); // 'number'
console.log(typeof isStudent); // 'boolean'
console.log(typeof job); // 'undefined'
console.log(typeof salary); // 'object' (historical bug in JavaScript)
console.log(typeof person); // 'object'
console.log(typeof colors); // 'object' (arrays are objects in JavaScript)
console.log(typeof greet); // 'function'
string
number
boolean
undefined
object
object
object
function
Operators
Operators are symbols that perform operations on operands (values and variables). JavaScript has several types of operators:
Arithmetic Operators
+
Addition-
Subtraction*
Multiplication/
Division%
Modulus (remainder)**
Exponentiation (ES2016)++
Increment--
Decrement
Assignment Operators
=
,+=
,-=
,*=
,/=
,%=
, etc.
Comparison Operators
==
Equal to (value only)===
Equal to (value and type)!=
Not equal to (value only)!==
Not equal to (value and type)>
Greater than<
Less than>=
Greater than or equal to<=
Less than or equal to
Logical Operators
&&
Logical AND||
Logical OR!
Logical NOT
Type Operators
typeof
Returns the type of a variableinstanceof
Returns true if an object is an instance of a specified type
// Arithmetic operators
let a = 10;
let b = 5;
let sum = a + b; // 15
let diff = a - b; // 5
let product = a * b; // 50
let quotient = a / b; // 2
let remainder = a % b; // 0
let power = a ** 2; // 100
// Increment and decrement
let counter = 1;
counter++; // counter is now 2
counter--; // counter is now 1
// Assignment operators
let x = 10;
x += 5; // Same as x = x + 5, x is now 15
x -= 3; // Same as x = x - 3, x is now 12
x *= 2; // Same as x = x * 2, x is now 24
x /= 4; // Same as x = x / 4, x is now 6
// Comparison operators
console.log(5 == "5"); // true (value equality)
console.log(5 === "5"); // false (strict equality - different types)
console.log(5 != "5"); // false
console.log(5 !== "5"); // true
console.log(10 > 5); // true
console.log(10 < 5); // false
// Logical operators
let isAdult = true;
let hasPermission = false;
console.log(isAdult && hasPermission); // false (both must be true)
console.log(isAdult || hasPermission); // true (at least one is true)
console.log(!isAdult); // false (negation)
true
false
false
true
true
false
false
true
false
Control Flow
Control flow statements determine the order in which code is executed. They allow you to make decisions, create loops, and handle exceptions.
Conditional Statements
if
statement: Executes a block of code if a condition is trueelse
statement: Executes a block of code if the same condition is falseelse if
statement: Specifies a new condition if the first condition is falseswitch
statement: Selects one of many code blocks to be executed
Loops
for
loop: Repeats a block of code a specified number of timeswhile
loop: Repeats a block of code while a condition is truedo...while
loop: Repeats a block of code while a condition is true (executes at least once)for...in
loop: Iterates over properties of an objectfor...of
loop: Iterates over values of an iterable object (arrays, strings, etc.)
Error Handling
try...catch
statement: Handles errors gracefully without stopping the scriptthrow
statement: Creates custom errorsfinally
block: Executes code regardless of the result
// If, else if, else statement
let hour = 15;
let greeting;
if (hour < 12) {
greeting = "Good morning";
} else if (hour < 18) {
greeting = "Good afternoon";
} else {
greeting = "Good evening";
}
console.log(greeting); // "Good afternoon"
// Switch statement
let day = 3;
let dayName;
switch (day) {
case 1:
dayName = "Monday";
break;
case 2:
dayName = "Tuesday";
break;
case 3:
dayName = "Wednesday";
break;
case 4:
dayName = "Thursday";
break;
case 5:
dayName = "Friday";
break;
default:
dayName = "Weekend";
}
console.log(dayName); // "Wednesday"
// For loop
console.log("For loop:")
for (let i = 0; i < 3; i++) {
console.log(i);
}
// While loop
console.log("While loop:")
let j = 0;
while (j < 3) {
console.log(j);
j++;
}
// Do-while loop
console.log("Do-while loop:")
let k = 0;
do {
console.log(k);
k++;
} while (k < 3);
// Error handling
try {
// Attempt to execute this code
let result = undefinedVariable / 10; // This will cause an error
} catch (error) {
console.log("An error occurred: " + error.message);
} finally {
console.log("This always executes");
}
Good afternoon
Wednesday
For loop:
0
1
2
While loop:
0
1
2
Do-while loop:
0
1
2
An error occurred: undefinedVariable is not defined
This always executes
Functions and Objects
Functions
Functions are blocks of code designed to perform a particular task. They are executed when they are called (invoked).
Function Declaration
There are several ways to define functions in JavaScript:
- Function Declaration: Uses the
function
keyword followed by a name - Function Expression: Assigns an anonymous function to a variable
- Arrow Functions (ES6): A shorter syntax using the
=>
symbol
Parameters and Arguments
Functions can take inputs (parameters) and return outputs. Parameters are listed in the function definition, while arguments are the values passed to the function when it's called.
Scope and Closures
Each function creates its own scope, which is the context where variables are accessible. A closure is a function that remembers its outer variables and can access them later, even when executed outside its original scope.
// Function Declaration
function greet(name) {
return 'Hello, ' + name + '!';
}
// Function Expression
const sayGoodbye = function(name) {
return 'Goodbye, ' + name + '!';
};
// Arrow Function (ES6)
const multiply = (a, b) => a * b;
// Calling functions
console.log(greet('John')); // "Hello, John!"
console.log(sayGoodbye('John')); // "Goodbye, John!"
console.log(multiply(5, 3)); // 15
// Default parameters (ES6)
function greetWithDefault(name = 'Guest') {
return 'Hello, ' + name + '!';
}
console.log(greetWithDefault()); // "Hello, Guest!"
// Rest parameters (ES6)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Closures
function createCounter() {
let count = 0;
return function() {
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Hello, John!
Goodbye, John!
15
Hello, Guest!
15
1
2
3
Objects
Objects are collections of key-value pairs, where the keys are strings (or Symbols) and the values can be any data type, including other objects and functions.
Creating Objects
There are several ways to create objects:
- Object literals: Using curly braces
{}
- Constructor functions: Using
new
keyword - Object.create(): Using a prototype object
- ES6 Classes: A more familiar syntax for object-oriented programming
Accessing Object Properties
You can access object properties using:
- Dot notation:
object.property
- Bracket notation:
object['property']
Object Methods
Functions stored as object properties are called methods. They can access and modify the object's properties using the this
keyword.
// Object literal
const person = {
firstName: 'John',
lastName: 'Doe',
age: 30,
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
};
// Accessing properties
console.log(person.firstName); // "John"
console.log(person['lastName']); // "Doe"
// Calling methods
console.log(person.fullName()); // "John Doe"
// Adding and modifying properties
person.email = 'john@example.com';
person.age = 31;
// Object.keys(), Object.values(), Object.entries()
console.log(Object.keys(person)); // ["firstName", "lastName", "age", "fullName", "email"]
console.log(Object.values(person)); // ["John", "Doe", 31, ƒ, "john@example.com"]
// ES6 enhanced object literals
const name = 'Computer';
const price = 1000;
const product = {
name, // Same as name: name
price, // Same as price: price
displayInfo() { // Method shorthand
return `${this.name}: $${this.price}`;
}
};
console.log(product.displayInfo()); // "Computer: $1000"
// Constructor function
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.getDescription = function() {
return `${this.year} ${this.make} ${this.model}`;
};
}
const myCar = new Car('Toyota', 'Corolla', 2020);
console.log(myCar.getDescription()); // "2020 Toyota Corolla"
// ES6 Classes
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const john = new Person('John', 'Smith');
console.log(john.getFullName()); // "John Smith"
John
Doe
John Doe
["firstName", "lastName", "age", "fullName", "email"]
Computer: $1000
2020 Toyota Corolla
John Smith
Arrays
Arrays are ordered collections of values. In JavaScript, arrays can hold mixed types of data and are dynamically sized.
Creating Arrays
You can create arrays using:
- Array literals:
[]
- Array constructor:
new Array()
Array Methods
JavaScript arrays come with many built-in methods for manipulation:
- Adding/Removing Elements:
push()
,pop()
,shift()
,unshift()
,splice()
- Finding Elements:
indexOf()
,find()
,filter()
- Transforming Arrays:
map()
,reduce()
,sort()
,reverse()
- Iterating:
forEach()
- Other Useful Methods:
concat()
,slice()
,join()
,includes()
// Creating arrays
let fruits = ['Apple', 'Banana', 'Orange'];
let mixed = [1, 'hello', true, {name: 'John'}, [1, 2, 3]];
// Accessing elements
console.log(fruits[0]); // "Apple"
console.log(fruits[fruits.length - 1]); // "Orange" (last element)
// Adding elements
fruits.push('Mango'); // Add to end
fruits.unshift('Strawberry'); // Add to beginning
console.log(fruits); // ["Strawberry", "Apple", "Banana", "Orange", "Mango"]
// Removing elements
let lastFruit = fruits.pop(); // Remove from end
let firstFruit = fruits.shift(); // Remove from beginning
console.log(fruits); // ["Apple", "Banana", "Orange"]
console.log(lastFruit); // "Mango"
console.log(firstFruit); // "Strawberry"
// Transforming arrays
let numbers = [1, 2, 3, 4, 5];
// map: Creates a new array by performing a function on each array element
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter: Creates a new array with elements that pass a test
let evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// reduce: Reduces the array to a single value
let sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // 15
// forEach: Executes a function for each array element
numbers.forEach(num => console.log(num));
// find: Returns the first element that passes a test
let found = numbers.find(num => num > 3);
console.log(found); // 4
// sort: Sorts the elements of an array
let unsortedNumbers = [3, 1, 4, 1, 5];
unsortedNumbers.sort();
console.log(unsortedNumbers); // [1, 1, 3, 4, 5]
// Custom sort function
let scores = [40, 100, 1, 5, 25, 10];
scores.sort((a, b) => a - b); // Ascending order
console.log(scores); // [1, 5, 10, 25, 40, 100]
Apple
Orange
["Strawberry", "Apple", "Banana", "Orange", "Mango"]
["Apple", "Banana", "Orange"]
Mango
Strawberry
[2, 4, 6, 8, 10]
[2, 4]
15
1
2
3
4
5
4
[1, 1, 3, 4, 5]
[1, 5, 10, 25, 40, 100]
DOM Manipulation
What is the DOM?
The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the structure of a document as a tree of objects, where each object corresponds to a part of the document.
The DOM provides a way for JavaScript to:
- Access and change document content, structure, and styles
- Add, modify, or delete HTML elements and attributes
- React to events like clicks, form submissions, etc.
The DOM is not a part of JavaScript; it's a Web API that browsers implement to allow scripts to interact with the web page.
// The document object is the entry point to the DOM
console.log(document.title); // Gets the page title
// DOM navigation
console.log(document.body); // The body element
console.log(document.head); // The head element
console.log(document.documentElement); // The html element
// Basic DOM structure example
/*
<html> (document.documentElement)
<head> (document.head)
<title>My Page</title>
</head>
<body> (document.body)
<h1 id="title">Hello World</h1>
<p class="content">This is a paragraph.</p>
</body>
</html>
*/
// Parent, children, and siblings
const body = document.body;
const firstChild = body.firstChild; // First child node
const firstElement = body.firstElementChild; // First element node
const childNodes = body.childNodes; // All child nodes
const children = body.children; // All element nodes
// DOM node types
// Element nodes: Regular HTML elements (type 1)
// Text nodes: Text content (type 3)
// Comment nodes: HTML comments (type 8)
// Document nodes: The document itself (type 9)
Selecting DOM Elements
JavaScript provides several methods to select elements from the DOM:
Basic Selectors
getElementById()
: Selects an element by its IDgetElementsByTagName()
: Selects all elements with the given tag namegetElementsByClassName()
: Selects all elements with the given class name
Query Selectors (More Powerful)
querySelector()
: Returns the first element that matches a CSS selectorquerySelectorAll()
: Returns all elements that match a CSS selector
Query selectors use CSS-style syntax, making them very versatile for complex selections.
// Assuming we have this HTML:
/*
<div id="container">
<h1 id="title">My Page</h1>
<p class="content">First paragraph</p>
<p class="content">Second paragraph</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
*/
// getElementById
const title = document.getElementById('title');
console.log(title); // <h1 id="title">My Page</h1>
// getElementsByTagName
const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs.length); // 2 (HTMLCollection of 2 items)
// getElementsByClassName
const contentElements = document.getElementsByClassName('content');
console.log(contentElements.length); // 2 (HTMLCollection of 2 items)
// querySelector - returns the first match
const firstParagraph = document.querySelector('p');
console.log(firstParagraph); // <p class="content">First paragraph</p>
// querySelectorAll - returns all matches
const allParagraphs = document.querySelectorAll('p');
console.log(allParagraphs.length); // 2 (NodeList of 2 items)
// Complex CSS selectors
const listItems = document.querySelectorAll('ul li');
const secondContentParagraph = document.querySelector('p.content:nth-child(3)');
const containedElements = document.querySelectorAll('#container > *');
// Converting collections to arrays
// HTMLCollection and NodeList are array-like, but not true arrays
const paragraphArray = Array.from(paragraphs);
// Or using spread syntax
const listItemArray = [...listItems];
// Now we can use array methods
paragraphArray.forEach(p => {
console.log(p.textContent);
});
Modifying the DOM
After selecting elements, you can modify them in various ways:
Changing Content
textContent
: Gets or sets the text content (without HTML)innerHTML
: Gets or sets the HTML content (including tags)innerText
: Gets or sets the visible text content
Modifying Attributes
getAttribute()
: Gets the value of an attributesetAttribute()
: Sets the value of an attributeremoveAttribute()
: Removes an attribute- Direct property access:
element.id
,element.src
, etc.
Manipulating Classes
classList.add()
: Adds a classclassList.remove()
: Removes a classclassList.toggle()
: Toggles a class on/offclassList.contains()
: Checks if an element has a class
Changing Styles
style
property: Directly modify inline stylesgetComputedStyle()
: Get applied styles (read-only)
Creating and Removing Elements
createElement()
: Creates a new elementappendChild()
: Adds a child elementinsertBefore()
: Inserts before another elementremoveChild()
: Removes a child elementremove()
: Removes the element itself
// Changing content
const title = document.querySelector('#title');
title.textContent = 'New Title'; // Changes text without HTML
title.innerHTML = 'New <em>Emphasized</em> Title'; // Parses and applies HTML
// Modifying attributes
const link = document.querySelector('a');
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');
// Shortcuts for common attributes
link.href = 'https://example.com';
link.target = '_blank';
// Manipulating classes
const paragraph = document.querySelector('p');
paragraph.classList.add('highlight'); // Adds a class
paragraph.classList.remove('old-class'); // Removes a class
paragraph.classList.toggle('active'); // Adds if absent, removes if present
const hasClass = paragraph.classList.contains('highlight'); // true
// Changing styles
paragraph.style.color = 'blue';
paragraph.style.backgroundColor = 'yellow';
paragraph.style.padding = '10px';
// Creating and adding elements
const newElement = document.createElement('div');
newElement.textContent = 'This is a new element';
newElement.className = 'new-div';
const container = document.querySelector('#container');
container.appendChild(newElement); // Adds at the end
// Insert before another element
const firstParagraph = document.querySelector('p');
const newParagraph = document.createElement('p');
newParagraph.textContent = 'Inserted paragraph';
container.insertBefore(newParagraph, firstParagraph);
// Removing elements
container.removeChild(newParagraph); // Remove specific child
// Or using the newer method
newElement.remove(); // Remove the element itself
New Emphasized Title
This is a paragraph.
Events
Events are actions or occurrences that happen in the browser, which can be detected and responded to with JavaScript. Common events include clicks, keyboard presses, form submissions, page loads, and more.
Adding Event Listeners
There are several ways to add event listeners:
addEventListener()
: The modern, recommended way- HTML attribute:
onclick="handler()"
(not recommended) - DOM property:
element.onclick = function() {}
(limited to one handler)
Common Events
- Mouse events:
click
,dblclick
,mouseenter
,mouseleave
,mouseover
,mouseout
,mousemove
- Keyboard events:
keydown
,keyup
,keypress
- Form events:
submit
,reset
,change
,input
,focus
,blur
- Document/Window events:
load
,resize
,scroll
,DOMContentLoaded
The Event Object
When an event occurs, the browser creates an event object with details about the event. This object is automatically passed to event handlers.
// Adding event listeners
const button = document.querySelector('#myButton');
// Method 1: addEventListener (recommended)
button.addEventListener('click', function(event) {
console.log('Button clicked!');
console.log(event); // The event object
});
// Method 2: DOM property (limited to one handler per event)
button.onclick = function() {
alert('Button clicked!');
};
// Method 3: HTML attribute (not recommended)
// <button onclick="alert('Button clicked!')">Click me</button>
// Event object properties
button.addEventListener('click', function(event) {
// Common properties
console.log(event.type); // "click"
console.log(event.target); // The element that triggered the event
console.log(event.currentTarget); // The element that the listener is attached to
console.log(event.timeStamp); // When the event occurred
// Mouse event properties
console.log(event.clientX, event.clientY); // Mouse position relative to viewport
console.log(event.button); // Which mouse button was pressed
});
// Prevent default behavior
const link = document.querySelector('a');
link.addEventListener('click', function(event) {
event.preventDefault(); // Prevents navigating to the link
console.log('Link click prevented');
});
// Stop propagation (event bubbling)
const child = document.querySelector('.child');
child.addEventListener('click', function(event) {
event.stopPropagation(); // Prevents the event from bubbling up
console.log('Child clicked, parent will not be notified');
});
// Event delegation (efficient for many similar elements)
const list = document.querySelector('ul');
list.addEventListener('click', function(event) {
// Check if a list item was clicked
if (event.target.tagName === 'LI') {
console.log('List item clicked:', event.target.textContent);
}
});
// Common events examples
// Form submission
const form = document.querySelector('form');
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent actual form submission
console.log('Form submitted');
// Get form data
const formData = new FormData(form);
for (let [key, value] of formData.entries()) {
console.log(key, value);
}
});
// Keyboard events
document.addEventListener('keydown', function(event) {
console.log('Key pressed:', event.key);
// Check for specific keys
if (event.key === 'Escape') {
console.log('Escape key pressed');
}
});
// Window events
window.addEventListener('resize', function() {
console.log('Window resized to:', window.innerWidth, 'x', window.innerHeight);
});
// DOMContentLoaded - when the HTML is fully loaded
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM fully loaded');
});
Asynchronous JavaScript
Introduction to Asynchronous JS
JavaScript is primarily single-threaded, which means it can only execute one piece of code at a time. However, many operations like fetching data from servers, reading files, or waiting for user input take time to complete.
Asynchronous programming is a technique that enables your program to start a potentially long-running task and still be able to be responsive to other events while that task runs, rather than having to wait until that task has finished.
Key Asynchronous Concepts
- Callbacks: Functions passed as arguments to be executed after another function completes
- Promises: Objects representing the eventual completion or failure of an asynchronous operation
- Async/Await: A newer, more readable way to work with promises
- Event Loop: JavaScript's mechanism for handling asynchronous operations
// Synchronous code (blocking)
console.log('Start');
function calculateSum() {
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
return sum;
}
// This blocks the execution until completed
// console.log('Sum:', calculateSum());
console.log('End');
// Asynchronous code (non-blocking)
console.log('Start');
setTimeout(() => {
console.log('This runs after 2 seconds');
}, 2000);
console.log('End');
// The event loop
console.log('First'); // Executes immediately (call stack)
setTimeout(() => {
console.log('Second'); // Executes after the specified delay (callback queue)
}, 0);
Promise.resolve().then(() => {
console.log('Third'); // Executes after synchronous code but before timeout (microtask queue)
});
console.log('Fourth'); // Executes immediately (call stack)
// Output will be: First, Fourth, Third, Second
Start
End
Start
End
First
Fourth
Third
Second
This runs after 2 seconds
Callbacks
A callback is a function passed as an argument to another function, which is then invoked inside the outer function to complete some kind of action. Callbacks are the oldest way of handling asynchronous operations in JavaScript.
How Callbacks Work
- A function (let's call it 'A') is passed as an argument to another function (let's call it 'B')
- Function B executes some operations, and when it finishes, it calls function A
- This allows function B to notify function A when it's done, even if B's work takes time
Callback Hell
When multiple asynchronous operations depend on each other, callbacks can lead to deeply nested code that's hard to read and maintain. This is often called "callback hell" or the "pyramid of doom".
// Basic callback example
function greet(name, callback) {
console.log('Hello, ' + name);
callback();
}
greet('John', function() {
console.log('Callback function executed!');
});
// Asynchronous callback with setTimeout
console.log('Starting...');
setTimeout(function() {
console.log('This runs after 2 seconds');
}, 2000);
console.log('Continuing execution...');
// Real-world example: loading a script
function loadScript(src, callback) {
const script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
// Using the loadScript function
loadScript('https://example.com/script.js', function(error, script) {
if (error) {
console.error('Error loading the script:', error);
} else {
console.log('Script loaded successfully:', script.src);
// Maybe load another script...
}
});
// Callback Hell example
function processData() {
fetchData(function(data) {
processStep1(data, function(result1) {
processStep2(result1, function(result2) {
processStep3(result2, function(result3) {
processStep4(result3, function(result4) {
console.log('Final result:', result4);
}, handleError);
}, handleError);
}, handleError);
}, handleError);
}, handleError);
}
function handleError(error) {
console.error('An error occurred:', error);
}
Hello, John
Callback function executed!
Starting...
Continuing execution...
This runs after 2 seconds
Promises
Promises were introduced in ES6 (2015) to handle asynchronous operations in a more elegant way than callbacks. A Promise is an object representing the eventual completion or failure of an asynchronous operation.
Promise States
A Promise can be in one of these states:
- Pending: Initial state, neither fulfilled nor rejected
- Fulfilled: The operation completed successfully
- Rejected: The operation failed
Promise Methods
then()
: Called when the promise is fulfilledcatch()
: Called when the promise is rejectedfinally()
: Called regardless of success or failure
Promise Chaining
One of the key advantages of promises is the ability to chain multiple asynchronous operations together in a readable way.
Promise Static Methods
Promise.all()
: Waits for all promises to resolvePromise.race()
: Waits for the first promise to resolve or rejectPromise.resolve()
: Returns a resolved promisePromise.reject()
: Returns a rejected promise
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
// Simulating an asynchronous operation
const success = true;
setTimeout(() => {
if (success) {
resolve('Operation successful!'); // Fulfilled
} else {
reject(new Error('Operation failed!')); // Rejected
}
}, 1000);
});
// Using a Promise
myPromise
.then((result) => {
console.log('Success:', result);
})
.catch((error) => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Promise settled (fulfilled or rejected)');
});
// Promise chaining
function fetchUserData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: 'John' });
}, 1000);
});
}
function fetchUserPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['Post 1', 'Post 2', 'Post 3']);
}, 1000);
});
}
fetchUserData()
.then((user) => {
console.log('User:', user);
return fetchUserPosts(user.id);
})
.then((posts) => {
console.log('Posts:', posts);
})
.catch((error) => {
console.error('Error:', error);
});
// Promise.all - wait for multiple promises
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) => setTimeout(() => resolve('foo'), 1000));
const promise3 = fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json());
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log('All promises resolved:', values);
})
.catch((error) => {
console.error('At least one promise rejected:', error);
});
// Promise.race - first to settle wins
const fastPromise = new Promise((resolve) => setTimeout(() => resolve('fast'), 100));
const slowPromise = new Promise((resolve) => setTimeout(() => resolve('slow'), 500));
Promise.race([fastPromise, slowPromise])
.then((result) => {
console.log('Fastest promise result:', result); // 'fast'
});
Success: Operation successful!
Promise settled (fulfilled or rejected)
User: {id: 1, name: 'John'}
Posts: ['Post 1', 'Post 2', 'Post 3']
Fastest promise result: fast
All promises resolved: [3, 'foo', {userId: 1, id: 1, title: '...', completed: false}]
Async/Await
Async/await, introduced in ES2017 (ES8), is syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous code. This makes asynchronous code more readable and easier to debug.
Key Components
async
: Keyword used to declare an asynchronous function that automatically returns a Promiseawait
: Keyword that pauses the execution of an async function until a Promise is settled
Benefits of Async/Await
- Cleaner, more readable code than promise chains
- Better error handling with try/catch blocks
- Easier debugging
Limitations
- Can only be used inside async functions
- Still uses Promises under the hood
// Basic async/await
async function fetchData() {
return 'Data fetched!';
}
// This is equivalent to:
// function fetchData() {
// return Promise.resolve('Data fetched!');
// }
// Using an async function
fetchData().then(data => console.log(data));
// Using await inside an async function
async function displayData() {
try {
const data = await fetchData();
console.log('Received:', data);
} catch (error) {
console.error('Error:', error);
}
}
displayData();
// Real-world example: fetching data from an API
async function fetchUserData(userId) {
try {
// await pauses execution until the Promise resolves
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
// Check if the request was successful
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
// Parse the JSON response
const userData = await response.json();
return userData;
} catch (error) {
console.error('Error fetching user data:', error);
throw error; // Re-throw to allow caller to handle
}
}
// Using the async function
async function displayUser() {
try {
const user = await fetchUserData(1);
console.log('User data:', user);
} catch (error) {
console.error('Failed to display user:', error);
}
}
displayUser();
// Sequential vs Parallel execution
// Sequential (each await waits for the previous to complete)
async function sequentialFetch() {
console.time('sequential');
const user1 = await fetchUserData(1);
const user2 = await fetchUserData(2);
const user3 = await fetchUserData(3);
console.timeEnd('sequential');
return [user1, user2, user3];
}
// Parallel (all fetches start at the same time)
async function parallelFetch() {
console.time('parallel');
const userPromises = [
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
];
// Wait for all promises to resolve
const users = await Promise.all(userPromises);
console.timeEnd('parallel');
return users;
}
// For loop with await
async function processItems(items) {
const results = [];
for (const item of items) {
// Process items sequentially
const result = await processItem(item);
results.push(result);
}
return results;
}
// Handling errors
async function fetchWithErrorHandling() {
try {
const result = await fetchDataThatMightFail();
return result;
} catch (error) {
console.error('Error in fetchWithErrorHandling:', error);
// Handle error or rethrow
throw error;
} finally {
// This runs regardless of success or failure
console.log('Fetch operation completed');
}
}
Data fetched!
Received: Data fetched!
User data: {id: 1, name: 'Leanne Graham', ...}