Mastering Asynchronous JavaScript with Async/Await π‘οΈ β
Join Tanjiro and the Demon Slayer Corps as we delve into the powerful realm of Asynchronous JavaScript. We'll explore async functions, await expressions, top-level await, and the intricacies of handling promises with Promise.all and Promise.allSettled. Just like the relentless battles against demons, mastering asynchronous JavaScript requires precision, timing, and a deep understanding of the flow!
Understanding Asynchronous JavaScript β
In the world of JavaScript, not everything happens instantly. Asynchronous operations allow your code to perform tasks like fetching data from an API without blocking the main thread. This ensures your application remains responsive and efficient.
Imagine asynchronous operations as Breathing Techniques π used by the Demon Slayersβallowing them to execute multiple maneuvers swiftly without delay.
The Power of async and await β
The Basics of async Functions β
Tanjiro's First async Technique
An async function is a function that returns a Promise. It allows you to write asynchronous code that looks synchronous, making it easier to read and maintain.
async function getCountryName1() {
const res = await fetch("https://restcountries.com/v3.1/all");
const countries = await res.json();
const result = countries.map((country) => country.name.common);
return result;
}How It Works:
asyncKeyword: Declares an asynchronous function.awaitKeyword: Pauses the function execution until the Promise is resolved.- Return Value: The function returns a Promise that resolves to the returned value.
Top-Level await π (Introduced in 2019) β
Top-level await allows you to use await outside of async functions, simplifying the code further.
async function getCountryName() {
const countries = await fetch("https://restcountries.com/v3.1/all").then(
(res) => res.json()
);
const result = countries.map((country) => country.name.common);
return result;
}
let result = await getCountryName();
console.log(result);Key Differences:
- Chaining with
.then(): Combinesawaitwith.then()for handling Promises. - Top-Level Usage: Enables
awaitat the top level, making the code cleaner.
Handling Multiple Promises with Promise.all and Promise.allSettled β
Understanding Promise.all β
Nezuko's Coordinated Defense
Promise.all takes an array of Promises and returns a single Promise that resolves when all of the input Promises resolve. If any Promise rejects, Promise.all rejects immediately.
async function getAllSettledPromise() {
console.log("Case 2: P4 reject");
let P4 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 2000);
});
let P5 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(8);
}, 1000);
});
let P6 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 4000);
});
// Output & When?
try {
const result = await Promise.all([P4, P5, P6]);
console.log("All settled: ", result);
} catch (msg) {
console.log("Oops: ", msg);
}
}
getAllSettledPromise();What Happens Here:
- P4: Rejects after 2 seconds.
- P5: Resolves after 1 second.
- P6: Resolves after 4 seconds.
- Outcome: Since P4 rejects,
Promise.allimmediately rejects, and thecatchblock is executed.
Using Promise.allSettled β
Zenitsu's Comprehensive Strategy
Unlike Promise.all, Promise.allSettled waits for all Promises to settle (either fulfilled or rejected) and provides their outcomes.
async function getAllSettledPromise() {
console.log("Case 2: P4 reject");
let P4 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 2000);
});
let P5 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(8);
}, 1000);
});
let P6 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 4000);
});
// Output & When?
const results = await Promise.allSettled([P4, P5, P6]);
console.log("All settled: ", results);
}
getAllSettledPromise();Outcome:
- P4: Rejected with reason
2. - P5: Fulfilled with value
8. - P6: Fulfilled with value
3. - Output:
All settled: [ { status: 'rejected', reason: 2 }, { status: 'fulfilled', value: 8 }, { status: 'fulfilled', value: 3 } ]
Additional Examples and Extended Topics β
Example 1: Sequential vs. Parallel Execution β
Inosuke's Dual Breathing Forms
Consider two asynchronous operations: fetching user data and fetching posts. Executing them sequentially vs. in parallel can impact performance.
Sequential Execution:
async function fetchUserAndPostsSequential() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return { user, posts };
}Parallel Execution:
async function fetchUserAndPostsParallel() {
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
return { user, posts };
}Comparison:
- Sequential: Total time is the sum of both operations.
- Parallel: Total time is the maximum time of the two operations.
Example 2: Error Handling with try...catch β
Kanao's Protective Shields
Handling errors gracefully ensures your application remains robust.
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch error:", error);
}
}
fetchData();Extended Topic: async Iterators and Generators β
Muzan's Complex Schemes
async iterators allow you to work with streams of asynchronous data.
async function* asyncGenerator() {
const data = [1, 2, 3];
for (const item of data) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield item;
}
}
(async () => {
for await (const num of asyncGenerator()) {
console.log(num);
}
})();Output:
1
2
3Each number is logged every second, demonstrating asynchronous iteration.
Tasks and Practice β
Task 1: Implementing Promise.allSettled β
Rengoku's Challenge
Modify the getAllSettledPromise function to use Promise.allSettled instead of Promise.all and handle the results accordingly.
Hint:
Use Promise.allSettled to retrieve the status of each Promise.
// Your Code HereAnswer
async function getAllSettledPromise() {
console.log("Case 2: P4 reject");
let P4 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 2000);
});
let P5 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(8);
}, 1000);
});
let P6 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 4000);
});
// Using Promise.allSettled
const results = await Promise.allSettled([P4, P5, P6]);
console.log("All settled: ", results);
}
getAllSettledPromise();Output:
Case 2: P4 reject
All settled: [
{ status: 'rejected', reason: 2 },
{ status: 'fulfilled', value: 8 },
{ status: 'fulfilled', value: 3 }
]Task 2: Fetching Data with Sequential and Parallel Execution β
Tengen's Dual Strategy
Create two functions, one that fetches user data and posts sequentially, and another that does so in parallel using Promise.all.
Hint:
Use the provided fetchUser and fetchPosts functions.
async function fetchUser() {
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
return res.json();
}
async function fetchPosts(userId) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?userId=${userId}`
);
return res.json();
}
// Implement fetchUserAndPostsSequential and fetchUserAndPostsParallelAnswer
Sequential Execution:
async function fetchUserAndPostsSequential() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return { user, posts };
}
fetchUserAndPostsSequential().then((data) => console.log(data));Parallel Execution:
async function fetchUserAndPostsParallel() {
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
return { user, posts };
}
fetchUserAndPostsParallel().then((data) => console.log(data));Task 3: Error Handling in Async Functions β
Gyomei's Resilience Training
Enhance the fetchData function to retry fetching data up to three times if it fails.
Hint:
Use a loop to attempt fetching and catch errors.
async function fetchDataWithRetry(url, retries = 3) {
// Your Code Here
}Answer
async function fetchDataWithRetry(url, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Attempt ${attempt}: Network response was not ok`);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
if (attempt === retries) {
throw new Error("All retry attempts failed");
}
}
}
}
fetchDataWithRetry("https://api.example.com/data")
.then((data) => console.log(data))
.catch((error) => console.error(error));Output (if all attempts fail):
Attempt 1 failed: Error: Attempt 1: Network response was not ok
Attempt 2 failed: Error: Attempt 2: Network response was not ok
Attempt 3 failed: Error: Attempt 3: Network response was not ok
Error: All retry attempts failedTask 4: Using async Iterators β
Shinobu's Advanced Maneuvers
Create an async generator that yields numbers from 1 to 5 with a delay of 500ms between each.
Hint:
Use async function* and await within the generator.
async function* numberGenerator() {
// Your Code Here
}
(async () => {
for await (const num of numberGenerator()) {
console.log(num);
}
})();Answer
async function* numberGenerator() {
for (let i = 1; i <= 5; i++) {
await new Promise((resolve) => setTimeout(resolve, 500));
yield i;
}
}
(async () => {
for await (const num of numberGenerator()) {
console.log(num);
}
})();Output:
1
2
3
4
5Extended Topics β
Handling Race Conditions with Promise.race β
Muzan's Unpredictable Nature
Promise.race returns the first settled Promise (fulfilled or rejected) among the provided Promises.
async function fetchWithTimeout(url, timeout) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Fetch timed out");
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
Promise.race([
fetchWithTimeout("https://api.example.com/data", 3000),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), 3000)
),
])
.then((data) => console.log("Data:", data))
.catch((error) => console.error("Error:", error));Use Case:
Implementing a timeout for fetch requests to prevent hanging indefinitely.
Chaining Promises for Sequential Operations β
Genya's Strategic Planning
Chain multiple Promises to perform operations in a specific order.
function firstOperation() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("First operation completed");
resolve(1);
}, 1000);
});
}
function secondOperation(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Second operation received value: ${value}`);
resolve(value + 1);
}, 1000);
});
}
firstOperation()
.then((result) => secondOperation(result))
.then((finalResult) => console.log(`Final Result: ${finalResult}`))
.catch((error) => console.error(error));Output:
First operation completed
Second operation received value: 1
Final Result: 2Common Pitfalls β
Common Pitfalls
- Ignoring Errors: Forgetting to handle rejected Promises can lead to unhandled promise rejections.
- Overusing
await: Usingawaitunnecessarily can lead to sequential execution when parallel execution is possible. - Not Using
try...catch: Failing to wrapawaitexpressions intry...catchblocks can make error handling difficult. - Top-Level
awaitLimitations: Not all environments support top-levelawait. Ensure compatibility or use it within modules. - Mixing
asyncand.then(): While possible, mixingasync/awaitwith.then()can make the code less readable.
Conclusion β
Mastering asynchronous JavaScript is like achieving Hashira levels in codingβtransforming your applications to be more efficient, responsive, and powerful.
asyncandawaitsimplify asynchronous code, making it look synchronous.- Promises like
Promise.allandPromise.allSettledallow handling multiple asynchronous operations effectively. - Error Handling ensures your application remains robust against failures.
- Advanced Techniques like
asynciterators andPromise.raceopen doors to more sophisticated control over asynchronous flows.
Happy coding, and may your JavaScript prowess reach Demon Slayer levels! π‘οΈβ¨