To add to @skillfulDev's excellent explanation, here's a quick code example that might clarify the structure:
// Simulating an asynchronous operation (like fetching data)
function fetchData(url, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url) {
resolve(`Data from ${url}`);
} else {
reject(new Error('URL is required'));
}
}, delay);
});
}
// Using Promises
console.log('--- Promise Example ---');
fetchData('/api/users', 1000)
.then(data => {
console.log('Promise success:', data);
return fetchData('/api/posts', 500); // Chaining promises
})
.then(postData => {
console.log('Promise success (posts):', postData);
})
.catch(error => {
console.error('Promise error:', error.message);
});
// Using Async/Await
console.log('--- Async/Await Example ---');
async function getUserAndPosts() {
try {
const userData = await fetchData('/api/users', 1200);
console.log('Async/Await success:', userData);
const postData = await fetchData('/api/posts', 700);
console.log('Async/Await success (posts):', postData);
} catch (error) {
console.error('Async/Await error:', error.message);
}
}
getUserAndPosts();
console.log('This message appears first, showing the async nature!');
Notice how the async/await
version reads almost like synchronous code, making it much easier to follow the flow of operations. The try...catch
block directly handles errors, similar to how you'd handle them in synchronous code.