Promises and Async/Await
In this lesson, we will delve into Promises and Async/Await, two essential concepts in JavaScript that allow for handling asynchronous operations in a more manageable and readable way. Asynchronous programming is crucial in JavaScript, especially for operations like fetching data from APIs, reading files, or performing time-consuming tasks without blocking the execution of other code.
1. Understanding Asynchronous Programming
Asynchronous programming allows a program to perform other tasks while waiting for an operation to complete. This is particularly important in JavaScript, which is single-threaded, meaning it can only execute one task at a time. By using asynchronous programming, we can avoid blocking the main thread and ensure a smoother user experience.
2. What are Promises?
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It is a way to handle asynchronous operations in a more manageable manner than using traditional callback functions.
2.1 Promise States
A Promise can be in one of three states:
- Pending: The initial state; the operation is not yet completed.
- Fulfilled: The operation completed successfully.
- Rejected: The operation failed.
2.2 Creating a Promise
You can create a Promise using the Promise
constructor, which takes a function (known as the executor) that has two parameters: resolve
and reject
.
Example
let myPromise = new Promise((resolve, reject) => {
let success = true; // Change this to false to simulate a failure
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed!");
}
});
2.3 Consuming a Promise
To handle the result of a Promise, you use the .then()
method for successful resolutions and the .catch()
method for rejections.
Example
myPromise
.then(result => {
console.log(result); // Outputs: Operation was successful!
})
.catch(error => {
console.error(error); // Outputs: Operation failed!
});
2.4 Chaining Promises
You can chain multiple .then()
methods to perform a sequence of asynchronous operations.
Example
let fetchData = new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched!");
}, 2000);
});
fetchData
.then(result => {
console.log(result); // Outputs: Data fetched!
return "Processing data...";
})
.then(result => {
console.log(result); // Outputs: Processing data...
});
3. Error Handling in Promises
Handling errors in Promises is crucial for maintaining the robustness of your code. You can handle errors in two ways:
- Using
.catch()
at the end of a Promise chain. - Using a try/catch block inside an
async
function when using async/await.
Example
let fetchDataWithError = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Failed to fetch data!");
}, 2000);
});
fetchDataWithError
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error); // Outputs: Failed to fetch data!
});
4. Introduction to Async/Await
Async/Await is syntactic sugar built on top of Promises that allows you to write asynchronous code in a more synchronous manner.
4.1 The async
Keyword
The async
keyword is placed before a function declaration to define an asynchronous function. This function always returns a Promise.
Example
async function fetchData() {
return "Data fetched!";
}
fetchData().then(result => {
console.log(result); // Outputs: Data fetched!
});
4.2 The await
Keyword
The await
keyword can only be used inside an async
function. It pauses the execution of the async
function until the Promise is resolved or rejected, making it easier to read and write asynchronous code.
Example
async function fetchData() {
let data = await new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched!");
}, 2000);
});
console.log(data); // Outputs: Data fetched!
}
fetchData();
5. Error Handling with Async/Await
Error handling can be done using a try/catch block within an async
function.
Example
async function fetchDataWithError() {
try {
let data = await new Promise((resolve, reject) => {
setTimeout(() => {
reject("Failed to fetch data!");
}, 2000);
});
console.log(data);
} catch (error) {
console.error(error); // Outputs: Failed to fetch data!
}
}
fetchDataWithError();
6. Practical Examples
6.1 Fetching Data from an API
Using async/await
, you can easily fetch data from an API.
Example
async function getUserData(userId) {
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
let userData = await response.json();
console.log(userData);
} catch (error) {
console.error("Error fetching user data:", error);
}
}
getUserData(1);
6.2 Sequential and Parallel Requests
You can make sequential and parallel requests using async/await
.
Sequential Requests:
async function fetchSequentialData() {
let user1 = await fetchUser(1);
let user2 = await fetchUser(2);
console.log(user1, user2);
}
fetchSequentialData();
Parallel Requests:
async function fetchParallelData() {
let user1Promise = fetchUser(1);
let user2Promise = fetchUser(2);
let [user1, user2] = await Promise.all([user1Promise, user2Promise]);
console.log(user1, user2);
}
fetchParallelData();
7. Coding Exercises
Create a JavaScript file named promisesAndAsync.js
and complete the following exercises:
- Create a Promise: Write a Promise that resolves after 2 seconds with a message "Resolved!".
- Fetch User Data: Write an
async
function that fetches user data from the JSONPlaceholder API and logs the user's name. - Error Handling: Modify the above function to handle errors gracefully.
Example Solutions:
// 1. Create a Promise
let myPromise = new Promise((resolve) => {
setTimeout(() => {
resolve("Resolved!");
}, 2000);
});
myPromise.then(message => {
console.log(message); // Outputs: Resolved!
});
// 2. Fetch User Data
async function fetchUserData(userId) {
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
let userData = await response.json();
console.log("User Name:", userData.name);
} catch (error) {
console.error("Error fetching user data:", error);
}
}
fetchUserData(1); // Outputs: User Name: Leanne Graham
// 3. Error Handling
async function fetchUserDataWithErrorHandling(userId) {
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) throw new Error("Network response was not ok");
let userData = await response.json();
console.log("User Name:", userData.name);
} catch (error) {
console.error("Error fetching user data:", error);
}
}
fetchUserDataWithErrorHandling(100); // Outputs: Error fetching user data: ...
Conclusion
In this lesson, we learned about Promises and the Async/Await syntax in JavaScript. These concepts are essential for handling asynchronous operations effectively, allowing for cleaner and more readable code. Understanding how to use Promises and Async/Await will significantly enhance your ability to work with asynchronous programming in JavaScript.
In the next lesson, we will cover Error Handling, where you'll learn how to manage exceptions and errors more effectively in your JavaScript applications.