Callback, Promise, Async/Await
Lesson: 1
// Callback
function f1(callback) { console.log('f1'); callback(); } function f2(callback) { console.log('f2'); callback(); } function f3() { console.log('f3'); } // Invoke f1(function() { f2(function() { f3(); }); });
Callback Functions
Theory/Concept:
• A callback is simply a function that is passed as an argument to another function, and it is executed after the main function has finished executing. This is one of the simplest ways to handle asynchronous code, though it can lead to “callback hell” if not managed properly.
Explanation:
• In this code, f1, f2, and f3 are executed one after another. After f1 finishes, it calls the callback, which is f2. After f2 finishes, it calls the callback, which is f3. This ensures that each function runs in sequence, even though they’re asynchronous in nature.
// Promise
function f1() { return new Promise((resolve) => { console.log('f1'); resolve(); }); } function f2() { return new Promise((resolve) => { console.log('f2'); resolve(); }); } function f3() { return new Promise((resolve) => { console.log('f3'); resolve(); }); } // Invoke (sequentially) f1() .then(f2) .then(f3);
Promises
Theory/Concept:
• A Promise in JavaScript is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises provide a cleaner way to handle asynchronous operations compared to callbacks.
Explanation:
• Each function (f1, f2, f3) returns a promise. The .then() method is used to chain these promises together. When f1 finishes, it resolves its promise, and f2 is then executed, followed by f3.
• Promises avoid the “callback hell” problem by flattening the chain of functions.
// Promise.all
function f1() { return new Promise((resolve) => { console.log('f1'); resolve(); }); } function f2() { return new Promise((resolve) => { console.log('f2'); resolve(); }); } function f3() { return new Promise((resolve) => { console.log('f3'); resolve(); }); } // Invoke (concurrently) Promise.all([f1(), f2(), f3()]);
Promise.all
Theory/Concept:
• 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. It allows you to run multiple promises in parallel and wait for all of them to complete before proceeding.
Explanation:
• In this example, f1, f2, and f3 are all executed simultaneously (or as close to it as possible). The Promise.all method waits for all of these promises to resolve before it proceeds. The order of execution might not be guaranteed, but since these functions are synchronous (just logging), they’ll likely log in the order they are written.
// Promise.race
function f1() { return new Promise((resolve) => { setTimeout(() => { console.log('f1'); resolve('f1 done'); }, 3000); // f1 will resolve after 3 seconds }); } function f2() { return new Promise((resolve) => { setTimeout(() => { console.log('f2'); resolve('f2 done'); }, 2000); // f2 will resolve after 2 seconds }); } function f3() { return new Promise((resolve) => { setTimeout(() => { console.log('f3'); resolve('f3 done'); }, 1000); // f3 will resolve after 1 second }); } // Invoke (race) Promise.race([f1(), f2(), f3()]) .then((result) => { console.log('Race finished with:', result); }) .catch((error) => { console.error('Race error:', error); });
Promise.race
Theory/Concept:
• Promise.race is a method that takes an array of promises and returns a single promise that resolves or rejects as soon as the first promise in the array resolves or rejects. Essentially, it’s a race between the promises, and whichever promise settles first (either resolves or rejects) determines the outcome of the Promise.race.
Explanation:
• In this example, we have three functions (f1, f2, f3) that each return a promise, but they resolve after different amounts of time (3 seconds, 2 seconds, and 1 second, respectively).
• When Promise.race is called, it waits for the first promise to settle (either resolve or reject). In this case, f3 will resolve first (after 1 second), so the promise returned by Promise.race will resolve with the value ‘f3 done’, and the other promises (f1 and f2) will not affect the result.
• The then block will execute, logging “Race finished with: f3 done”.
Why Use Promise.race?
• Promise.race is particularly useful when you want to take action based on whichever asynchronous operation completes first. For example, you might use it to implement a timeout for an operation: if the operation takes too long, you could resolve a timeout promise first, thereby canceling the original operation.
In Summary:
• Promise.race allows you to run multiple promises in parallel and returns the result of the first promise that settles (resolves or rejects). It’s like a race where only the first to cross the finish line matters, making it useful for scenarios where the fastest response is needed.
// Promise.allSettled
function f1() { return new Promise((resolve) => { console.log('f1'); resolve(); }); } function f2() { return new Promise((resolve) => { console.log('f2'); resolve(); }); } function f3() { return new Promise((resolve) => { console.log('f3'); resolve(); }); } // Invoke (concurrently) Promise.allSettled([f1(), f2(), f3()]) .then((results) => { results.forEach((result) => console.log(result.status)); });
Promise.allSettled
Theory/Concept:
• Promise.allSettled is similar to Promise.all, but it waits for all promises to settle (either resolved or rejected). This is useful when you want to know the outcome of each promise, regardless of whether it succeeded or failed.
Explanation:
• Here, f1, f2, and f3 run concurrently. After all promises have either resolved or rejected, the then block will execute, logging the status of each promise (which will be “fulfilled” in this case because the functions don’t reject).
// Async/Await
async function f1() { console.log('f1'); } async function f2() { console.log('f2'); } async function f3() { console.log('f3'); } // Invoke (sequentially) async function invoke() { await f1(); await f2(); await f3(); } invoke();
Async/Await
Theory/Concept:
• async/await is built on top of promises and provides a more synchronous-like syntax for handling asynchronous code. It allows you to write asynchronous code that looks like synchronous code, making it easier to read and understand.
Explanation:
• The async keyword is added to a function to make it return a promise. The await keyword is used to pause the execution of the async function until the promise resolves. This allows f1, f2, and f3 to execute one after the other in a very readable manner.
In Summary:
• Callbacks: Basic way of chaining asynchronous code, but can lead to deeply nested code (callback hell).
• Promises: A cleaner, more manageable way to handle asynchronous code, with methods like .then() and .catch().
• Promise.all: Run multiple promises concurrently and wait for all to resolve.
• Promise.allSettled: Run multiple promises concurrently and wait for all to settle (resolve or reject).
• Async/Await: Syntactic sugar over promises, allowing you to write asynchronous code in a more synchronous style, making it easier to read and debug.
Callback, Promise, Async/Await
Lesson: 2
// Callback
function f1(callback) { console.log("Hello!"); if (callback) callback(); } function f2(callback) { setTimeout(() => { console.log("Goodbye!"); if (callback) callback(); }, 1000); f1(); } function f3() { f2(() => { let x = false; if (x) { console.log("Thank you!"); } else { console.log("Error"); } console.log("See you again!"); }); } f3();
// Promise
function f1() { return new Promise((resolve) => { console.log("Hello!"); resolve(); }); } function f2() { return new Promise((resolve) => { setTimeout(() => { console.log("Goodbye!"); resolve(); }, 1000); f1(); }); } function f3() { f2() .then(() => { return new Promise((resolve, reject) => { let x = false; if (x) { resolve("Thank you!"); } else { reject("Error"); } }); }) .then((message) => { console.log(message); }) .catch((error) => { console.log(error); }) .finally(() => { console.log("See you again!"); }); } f3();
// Promise.all
function f1() { return new Promise((resolve) => { console.log("Hello!"); resolve(); }); } function f2() { return new Promise((resolve) => { setTimeout(() => { console.log("Goodbye!"); resolve(); }, 1000); }); } function f3() { Promise.all([f1(), f2()]) .then(() => { return new Promise((resolve, reject) => { let x = false; if (x) { resolve("Thank you!"); } else { reject("Error"); } }); }) .then((message) => { console.log(message); }) .catch((error) => { console.log(error); }) .finally(() => { console.log("See you again!"); }); } f3();
// Promise.race
function f1() { return new Promise((resolve) => { console.log("Hello!"); resolve(); }); } function f2() { return new Promise((resolve) => { setTimeout(() => { console.log("Goodbye!"); resolve(); }, 1000); }); } function f3() { Promise.race([f1(), f2()]) .then(() => { return new Promise((resolve, reject) => { let x = false; if (x) { resolve("Thank you!"); } else { reject("Error"); } }); }) .then((message) => { console.log(message); }) .catch((error) => { console.log(error); }) .finally(() => { console.log("See you again!"); }); } f3();
// Promise.allSettled
function f1() { return new Promise((resolve) => { console.log("Hello!"); resolve(); }); } function f2() { return new Promise((resolve) => { setTimeout(() => { console.log("Goodbye!"); resolve(); }, 1000); }); } function f3() { Promise.allSettled([f1(), f2()]) .then(() => { return new Promise((resolve, reject) => { let x = false; if (x) { resolve("Thank you!"); } else { reject("Error"); } }); }) .then((message) => { console.log(message); }) .catch((error) => { console.log(error); }) .finally(() => { console.log("See you again!"); }); } f3();
// Async/Await
function f1() { return new Promise((resolve) => { console.log("Hello!"); resolve(); }); } function f2() { return new Promise((resolve) => { setTimeout(() => { console.log("Goodbye!"); resolve(); }, 1000); }); } async function f3() { await f1(); await f2(); try { let x = false; if (x) { console.log("Thank you!"); } else { throw new Error("Error"); } } catch (error) { console.log(error.message); } finally { console.log("See you again!"); } } f3();
// Sync-Async
function f1() { console.log("Hello!"); } function f2() { setTimeout((f2)=>{console.log("Goodbye!");}, 1000) f1(); }; function f3() { f2(); new Promise((resolve, reject)=>{ let x = false; if(x){ resolve("Thank you!") }else { reject("Error") } }) .then((message) => {console.log(message)}) .catch((error) => {console.log(error)}) .finally(()=> {console.log("See you again!")}) } f3();
This code involves a mix of synchronous and asynchronous behavior:
• f1() simply logs “Hello!”.
• f2() does two things:
• It schedules a message “Goodbye!” to be logged after 1 second using setTimeout.
• It immediately calls f1(), which logs “Hello!”.
• f3() calls f2() and then creates a promise that:
• Resolves with “Thank you!” if x is true.
• Rejects with “Error” if x is false.
• Regardless of the outcome, logs “See you again!” in the finally block.
When f3() is invoked:
1. “Hello!” is logged immediately by f1() (called from f2()).
2. “Goodbye!” is logged after 1 second (from setTimeout in f2()).
3. The promise in f3() rejects with “Error”, so “Error” is logged, followed by “See you again!”.
1. Callback Version
You can add callbacks to handle asynchronous behavior, but note that the original f3 already handles the promise using .then() and .catch(). Here’s how you could add callbacks:
Summary of Combinations:
1. Callback Version: Manually chain the execution using callbacks.
2. Promise Version: Chain the execution using .then() and .catch().
3. Promise.all Version: Wait for multiple promises to resolve and then proceed.
4. Promise.allSettled Version: Wait for all promises to settle (resolve or reject) and then proceed.
5. Promise.race Version: Proceed as soon as the first promise settles (resolve or reject).
6. Async/Await Version: Use async/await to write asynchronous code in a more synchronous style.
Each approach gives you a different level of control over how the functions execute and handle asynchronous behavior.
Callback, Promise, Async/Await
Lesson: 3
// Callback
function fetchData(url, callback) { fetch(url) .then(response => response.json()) .then(data => callback(null, data)) .catch(error => callback(error, null)); } function handleData(error, data) { if (error) { console.error("An error occurred:", error); } else { console.log("Data received:", data); } } // Example URL (replace with any valid API endpoint) const url = 'https://jsonplaceholder.org/users'; // Invoke fetchData(url, handleData);
// Explanation:
// • fetchData takes a url and a callback function.
// • Inside fetchData, fetch(url) is called to retrieve data.
// • If the data is successfully retrieved, it calls callback(null, data).
// • If there’s an error, it calls callback(error, null).
// Promise
function fetchData(url) { return fetch(url) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }); } // Example URL const url = 'https://jsonplaceholder.org/users'; fetchData(url) .then(data => { console.log("Data received:", data); }) .catch(error => { console.error("An error occurred:", error); });
// Explanation:
// • fetchData returns a promise that resolves with the JSON data if the request is successful.
// • .then() is used to handle the successful data retrieval, and .catch() handles any errors.
// Promise.all
const urls = [ 'https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/posts/2', 'https://jsonplaceholder.typicode.com/posts/3' ]; function fetchData(url) { return fetch(url).then(response => response.json()); } Promise.all(urls.map(url => fetchData(url))) .then(dataArray => { console.log("All data received:", dataArray); }) .catch(error => { console.error("An error occurred:", error); });
// Explanation:
// • Promise.all takes an array of promises and resolves when all of them are resolved.
// • This is useful when you need to fetch multiple resources and wait for all of them to be available.
// Promise.race
const urls = [ 'https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/posts/2' ]; function fetchData(url) { return fetch(url).then(response => response.json()); } Promise.race(urls.map(url => fetchData(url))) .then(data => { console.log("First data received:", data); }) .catch(error => { console.error("An error occurred:", error); });
// Explanation:
// • Promise.race is useful when you want the result of whichever fetch completes first.
// • This could be used in scenarios where you want to use the fastest response among multiple possible sources.
// Promise.allSettled
const urls = [ 'https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/invalid-url', // Invalid URL to cause an error 'https://jsonplaceholder.typicode.com/posts/3' ]; function fetchData(url) { return fetch(url).then(response => response.json()); } Promise.allSettled(urls.map(url => fetchData(url))) .then(results => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Data from URL ${index + 1}:`, result.value); } else { console.error(`Error with URL ${index + 1}:`, result.reason); } }); });
// Explanation:
// • Promise.allSettled returns a promise that resolves after all of the given promises have either resolved or rejected.
// • It provides the status of each promise, which you can handle accordingly.
// Async/Await
async function fetchData(url) { const response = await fetch(url); if (!response.ok) { throw new Error('Network response was not ok'); } const data = await response.json(); return data; } async function fetchMultipleData() { try { const url = 'https://jsonplaceholder.typicode.com/posts/1'; const data = await fetchData(url); console.log("Data received:", data); } catch (error) { console.error("An error occurred:", error); } } fetchMultipleData();
// Explanation:
// • The async function fetchData waits for the fetch() promise to resolve and then for the JSON data to be parsed.
// • fetchMultipleData demonstrates how to call fetchData using await, handling errors with try/catch.
Summary of Concepts:
• Callback: Functions passed as arguments to handle asynchronous operations, but can lead to “callback hell.”
• Promise: A more modern way to handle asynchronous operations with .then() and .catch().
• Promise.all: Wait for multiple promises to resolve; great for fetching multiple resources simultaneously.
• Promise.allSettled: Wait for all promises to settle, whether they resolve or reject.
• Promise.race: Returns the result of the first promise that settles; useful for picking the fastest operation.
• Async/Await: Syntactic sugar over promises that allows you to write asynchronous code in a more synchronous style.
Each of these examples uses fetch() to demonstrate how to handle asynchronous operations in different ways, helping you understand the theory and practical application of each method.
A callback is a function passed as an argument to another function. It is executed after the completion of a task.
function downloadFile(url, callback) { console.log(`Starting download from ${url}...`); setTimeout(() => { console.log(`Finished downloading from ${url}`); callback(); }, 2000); // Simulates a 2-second download time } function processFile() { console.log('Processing the downloaded file...'); } // Usage downloadFile('http://example.com/file', processFile); // Output: // Starting download from http://example.com/file... // Finished downloading from http://example.com/file // Processing the downloaded file...
A promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
function downloadFile(url) {
return new Promise((resolve, reject) => {
console.log(`Starting download from ${url}...`);
setTimeout(() => {
console.log(`Finished downloading from ${url}`);
resolve('Download complete');
}, 2000); // Simulates a 2-second download time
});
}
// Usage
downloadFile('http://example.com/file')
.then((message) => {
console.log(message); // Logs "Download complete"
console.log('Processing the downloaded file...');
})
.catch((error) => {
console.error('Download failed:', error);
});
async/await is syntactic sugar built on top of promises, making asynchronous code easier to write and read.
function downloadFile(url) { return new Promise((resolve, reject) => { console.log(`Starting download from ${url}...`); setTimeout(() => { console.log(`Finished downloading from ${url}`); resolve('Download complete'); }, 2000); // Simulates a 2-second download time }); } async function downloadAndProcess() { try { const message = await downloadFile('http://example.com/file'); console.log(message); // Logs "Download complete" console.log('Processing the downloaded file...'); } catch (error) { console.error('Download failed:', error); } } // Usage downloadAndProcess();
Promise.all takes an array of promises and returns a single promise that resolves when all the promises have resolved or rejects as soon as one of the promises rejects.
function downloadFile(url, time) {
return new Promise((resolve, reject) => {
console.log(`Starting download from ${url}...`);
setTimeout(() => {
if (Math.random() > 0.2) {
console.log(`Finished downloading from ${url}`);
resolve(`${url} download complete`);
} else {
reject(`${url} download failed`);
}
}, time);
});
}
const download1 = downloadFile('http://example.com/file1', 1000);
const download2 = downloadFile('http://example.com/file2', 2000);
const download3 = downloadFile('http://example.com/file3', 3000);
Promise.all([download1, download2, download3])
.then((messages) => {
console.log('All downloads complete:', messages);
})
.catch((error) => {
console.error('One or more downloads failed:', error);
});
Promise.allSettled takes an array of promises and returns a single promise that resolves when all the promises have settled (either resolved or rejected). It never rejects.
function downloadFile(url, time) { return new Promise((resolve, reject) => { console.log(`Starting download from ${url}...`); setTimeout(() => { if (Math.random() > 0.2) { console.log(`Finished downloading from ${url}`); resolve(`${url} download complete`); } else { reject(`${url} download failed`); } }, time); }); } const download1 = downloadFile('http://example.com/file1', 1000); const download2 = downloadFile('http://example.com/file2', 2000); const download3 = downloadFile('http://example.com/file3', 3000); Promise.allSettled([download1, download2, download3]) .then((results) => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Download ${index + 1} succeeded:`, result.value); } else { console.log(`Download ${index + 1} failed:`, result.reason); } }); });