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:
async
Keyword: Declares an asynchronous function.await
Keyword: 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()
: Combinesawait
with.then()
for handling Promises. - Top-Level Usage: Enables
await
at 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.all
immediately rejects, and thecatch
block 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
3
Each 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 Here
Answer
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 fetchUserAndPostsParallel
Answer
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 failed
Task 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
5
Extended 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: 2
Common Pitfalls β
Common Pitfalls
- Ignoring Errors: Forgetting to handle rejected Promises can lead to unhandled promise rejections.
- Overusing
await
: Usingawait
unnecessarily can lead to sequential execution when parallel execution is possible. - Not Using
try...catch
: Failing to wrapawait
expressions intry...catch
blocks can make error handling difficult. - Top-Level
await
Limitations: Not all environments support top-levelawait
. Ensure compatibility or use it within modules. - Mixing
async
and.then()
: While possible, mixingasync/await
with.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.
async
andawait
simplify asynchronous code, making it look synchronous.- Promises like
Promise.all
andPromise.allSettled
allow handling multiple asynchronous operations effectively. - Error Handling ensures your application remains robust against failures.
- Advanced Techniques like
async
iterators andPromise.race
open doors to more sophisticated control over asynchronous flows.
Happy coding, and may your JavaScript prowess reach Demon Slayer levels! π‘οΈβ¨