What is a Promise?
A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It can be in one of three states:
- Pending: The initial state. The operation is still in progress.
- Fulfilled: The operation completed successfully, and the promise has a resolved value.
- Rejected: The operation failed, and the promise has a reason for the failure (usually an error).
Creating a Promise
You can create a promise using the Promise constructor, which takes a function (called the executor) with two parameters: resolve and reject.
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const success = true; // Simulate success or failure
if (success) {
resolve("Operation successful!");
} else {
reject("Operation failed!");
}
}, 1000);
});
resolve(value): Called when the operation succeeds. Thevalueis the result of the operation.reject(reason): Called when the operation fails. Thereasonis typically an error object or message.
Consuming a Promise
Once a promise is created, you can handle its result using .then(), .catch(), and .finally().
.then(onFulfilled, onRejected): Attaches callbacks for when the promise is fulfilled or rejected.onFulfilled: A function to handle the resolved value.onRejected: A function to handle the rejection reason (optional).
.catch(onRejected): A shorthand for handling only rejections..finally(onFinally): Executes a callback when the promise is settled (either fulfilled or rejected). Useful for cleanup.
myPromise
.then((result) => {
console.log(result); // "Operation successful!"
})
.catch((error) => {
console.error(error); // "Operation failed!"
})
.finally(() => {
console.log("Promise settled."); // Runs regardless of success or failure
});
Chaining Promises
Promises can be chained to perform sequential asynchronous operations. Each .then() returns a new promise, allowing you to chain multiple .then() calls.
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Data fetched"), 1000);
});
};
const processData = (data) => {
return new Promise((resolve) => {
setTimeout(() => resolve(`${data} and processed`), 1000);
});
};
fetchData()
.then((data) => processData(data))
.then((result) => {
console.log(result); // "Data fetched and processed"
})
.catch((error) => {
console.error(error);
});
Promise Static Methods
ES6+ provides several static methods on the Promise object:
Promise.resolve(value): Returns a promise that is resolved with the given value.Promise.reject(reason): Returns a promise that is rejected with the given reason.Promise.all(iterable): Waits for all promises in the iterable to resolve, or rejects if any promise rejects.Promise.race(iterable): Waits for the first promise in the iterable to settle (either resolve or reject).Promise.allSettled(iterable): Waits for all promises to settle, regardless of whether they resolve or reject.Promise.any(iterable): Waits for the first promise to resolve, or rejects if all promises reject.
// Example of Promise.all
const promise1 = Promise.resolve("First");
const promise2 = new Promise((resolve) => setTimeout(resolve, 1000, "Second"));
const promise3 = Promise.reject("Error");
Promise.all([promise1, promise2])
.then((values) => {
console.log(values); // ["First", "Second"]
})
.catch((error) => {
console.error(error); // Not called
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // Not called
})
.catch((error) => {
console.error(error); // "Error"
});
Async/Await (Syntactic Sugar for Promises)
ES8 introduced async and await, which make working with promises even more intuitive. An async function automatically returns a promise, and await pauses the execution of the function until the promise is settled.
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Data fetched"), 1000);
});
};
const processData = async () => {
try {
const data = await fetchData();
console.log(data); // "Data fetched"
} catch (error) {
console.error(error);
}
};
processData();
Summary
- Promises represent asynchronous operations and have three states: pending, fulfilled, and rejected.
- Use
.then(),.catch(), and.finally()to handle promise results. - Chain promises for sequential asynchronous tasks.
- Use static methods like
Promise.all,Promise.race, etc., for advanced use cases. async/awaitprovides a cleaner syntax for working with promises.
Promises are a powerful tool for managing asynchronous code in JavaScript, making it easier to write and maintain complex workflows.
Examples
Example 1: Fetching Data from an API
One of the most common use cases for Promises is making HTTP requests to an API using the fetch function.
// Fetch data from a public API
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // Parse the JSON data
})
.then((data) => {
console.log('Post:', data); // Log the fetched data
})
.catch((error) => {
console.error('Error:', error); // Handle any errors
});
Explanation:
fetchreturns a Promise that resolves to theResponseobject.- The first
.then()checks if the response is OK and parses the JSON data. - The second
.then()logs the parsed data. .catch()handles any errors that occur during the process.
Example 2: Simulating a Database Query
Promises are often used to simulate asynchronous operations like querying a database.
// Simulate a database query
const queryDatabase = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John Doe', email: '[email protected]' };
const error = Math.random() > 0.5; // Simulate a random error
if (error) {
reject('Database query failed!');
} else {
resolve(data);
}
}, 1000); // Simulate a 1-second delay
});
};
// Use the queryDatabase function
queryDatabase()
.then((user) => {
console.log('User:', user); // Log the user data
})
.catch((error) => {
console.error('Error:', error); // Handle the error
});
Explanation:
queryDatabasesimulates a database query that takes 1 second to complete.- It randomly succeeds or fails to demonstrate error handling.
.then()handles the resolved data, and.catch()handles the rejection.
Example 3: Chaining Promises
Promises can be chained to perform sequential asynchronous tasks.
// Simulate fetching user data and then their posts
const fetchUser = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: 'Alice' });
}, 1000);
});
};
const fetchPosts = (userId) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
}, 1000);
});
};
// Chain the promises
fetchUser()
.then((user) => {
console.log('User:', user);
return fetchPosts(user.id); // Return a new promise
})
.then((posts) => {
console.log('Posts:', posts);
})
.catch((error) => {
console.error('Error:', error);
});
Explanation:
fetchUsersimulates fetching user data.- Once the user data is resolved,
fetchPostsis called with the user’s ID. - The second
.then()logs the posts. .catch()handles any errors in the chain.
Example 4: Using Promise.all
Promise.all is useful when you need to wait for multiple asynchronous operations to complete.
// Simulate multiple API calls
const fetchUser = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: 'Bob' });
}, 1000);
});
};
const fetchPosts = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
}, 1500);
});
};
// Wait for both promises to resolve
Promise.all([fetchUser(), fetchPosts()])
.then(([user, posts]) => {
console.log('User:', user);
console.log('Posts:', posts);
})
.catch((error) => {
console.error('Error:', error);
});
Explanation:
Promise.alltakes an array of promises and waits for all of them to resolve.- The
.then()callback receives an array of results in the same order as the input promises. - If any promise rejects,
.catch()handles the error.
Example 5: Using async/await with Promises
async/await provides a cleaner way to work with Promises.
// Simulate an asynchronous operation
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Data fetched!');
}, 1000);
});
};
// Use async/await to handle the promise
const processData = async () => {
try {
const data = await fetchData(); // Wait for the promise to resolve
console.log(data); // Log the result
} catch (error) {
console.error('Error:', error); // Handle errors
}
};
processData();
Explanation:
fetchDatasimulates an asynchronous operation.processDatais anasyncfunction that usesawaitto wait for the promise to resolve.try/catchhandles errors gracefully.
Example 6: Real-World File Reading with fs.promises
In Node.js, the fs.promises module provides Promise-based file system operations.
const fs = require('fs').promises;
// Read a file asynchronously
fs.readFile('example.txt', 'utf8')
.then((data) => {
console.log('File content:', data);
})
.catch((error) => {
console.error('Error reading file:', error);
});
Explanation:
fs.promises.readFilereads a file and returns a Promise.- The
.then()callback logs the file content. .catch()handles any errors, such as the file not existing.
Example 7: Real-World API Call with axios
axios is a popular library for making HTTP requests and uses Promises under the hood.
const axios = require('axios');
// Fetch data from an API
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
console.log('Post:', response.data);
})
.catch((error) => {
console.error('Error:', error.message);
});
Explanation:
axios.getreturns a Promise that resolves to the API response.- The
.then()callback logs the response data. .catch()handles any errors, such as network issues.
These examples demonstrate how Promises are used in real-world scenarios, from fetching data to handling file operations and more. They provide a clean and structured way to manage asynchronous code in JavaScript.