Advanced JavaScript - Promises, Network (API), Storage

Table of contents
- ๐ Intro to Promises
- โก Microtask Queue
- ๐ Function that Returns Promise
- โฐ Promise and setTimeout
- โจ Promise.resolve and More About then Method
- ๐ง Convert Nested Callbacks to Flat Code Using Promises
- ๐ Intro to Ajax, HTTP Request
- ๐ Fetch API
- โ ๏ธ Error Handling in Fetch API
- ๐ก Axios API
- ๐ฏ Consume Promises with Async and Await
- ๐พ Local Storage
- ๐ Session Storage
- ๐ Practical Example: Complete Data Fetching with Storage
- โ Summary

๐ Intro to Promises
โ Definition:
A Promise is an object that represents the eventual completion or failure of an asynchronous operation and its resulting value. Promises provide a cleaner way to handle asynchronous code compared to callbacks, avoiding callback hell. They have three states: pending, fulfilled (resolved), or rejected, and allow chaining of operations.
๐ Syntax:
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
if (success) resolve(value);
else reject(error);
});
๐งช Example:
const orderPizza = new Promise((resolve, reject) => {
let pizzaReady = true;
setTimeout(() => {
if (pizzaReady) {
resolve("Pizza is ready! ๐");
} else {
reject("Pizza could not be made ๐");
}
}, 2000);
});
orderPizza
.then(message => console.log(message))
.catch(error => console.log(error));
โก Microtask Queue
โ Definition:
The microtask queue is a special queue that has higher priority than the regular callback queue (task queue) in JavaScript's event loop. Promise callbacks (.then, .catch, .finally) are placed in the microtask queue and are executed before any tasks from the callback queue. This ensures that promise resolutions are handled immediately after the current execution stack is empty.
๐ Syntax:
// Microtasks (higher priority)
Promise.resolve().then(() => console.log('Microtask'));
// Macrotasks (lower priority)
setTimeout(() => console.log('Macrotask'), 0);
๐งช Example:
console.log('1'); // Synchronous
setTimeout(() => console.log('2 - setTimeout'), 0); // Macrotask
Promise.resolve().then(() => console.log('3 - Promise')); // Microtask
console.log('4'); // Synchronous
// Output: 1, 4, 3 - Promise, 2 - setTimeout
๐ Function that Returns Promise
โ Definition:
A function that returns a promise allows you to create reusable asynchronous operations that can be chained and handled consistently. These functions encapsulate async logic and return a promise object that resolves or rejects based on the operation's outcome. This pattern enables better code organization and reusability for async operations.
๐ Syntax:
function asyncFunction() {
return new Promise((resolve, reject) => {
// Async operation logic
});
}
๐งช Example:
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
const users = { 1: 'Alice', 2: 'Bob', 3: 'Charlie' };
setTimeout(() => {
if (users[userId]) {
resolve({ id: userId, name: users[userId] });
} else {
reject(`User with ID ${userId} not found`);
}
}, 1000);
});
}
// Usage
fetchUserData(1)
.then(user => console.log('User:', user.name))
.catch(error => console.log('Error:', error));
โฐ Promise and setTimeout
โ Definition:
Combining promises with setTimeout allows you to create delayed asynchronous operations that resolve or reject after a specified time period. This combination is useful for simulating API calls, creating delays in async flows, or implementing timeout functionality. The setTimeout function runs in the background while the promise provides a clean interface for handling the delayed result.
๐ Syntax:
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), ms);
});
}
๐งช Example:
function delayedMessage(message, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (message) {
resolve(`Message: ${message}`);
} else {
reject('No message provided');
}
}, delay);
});
}
delayedMessage('Hello World!', 2000)
.then(result => console.log(result))
.catch(error => console.log(error));
// Output after 2 seconds: Message: Hello World!
โจ Promise.resolve and More About then Method
โ Definition:
Promise.resolve() creates a promise that is immediately resolved with the given value, useful for converting non-promise values into promises. The .then() method always returns a new promise, enabling method chaining and allowing you to transform values through multiple steps. If you return a value from .then(), it's automatically wrapped in a resolved promise.
๐ Syntax:
Promise.resolve(value).then(result => newValue);
// Chain multiple .then() calls
promise.then(value => transform1).then(value => transform2);
๐งช Example:
// Promise.resolve example
Promise.resolve(5)
.then(value => {
console.log('Initial value:', value); // 5
return value * 2;
})
.then(value => {
console.log('Doubled value:', value); // 10
return value + 10;
})
.then(value => {
console.log('Final value:', value); // 20
});
// Chaining with string manipulation
Promise.resolve('hello')
.then(str => str + ' world')
.then(str => str.toUpperCase())
.then(str => console.log(str)); // HELLO WORLD
๐ง Convert Nested Callbacks to Flat Code Using Promises
โ Definition:
Converting callback hell to promises involves replacing nested callback functions with promise chains using .then() methods. This transformation eliminates the pyramid-shaped indentation and makes error handling more consistent with .catch(). Promise chaining creates a linear, readable flow that's easier to maintain and debug than deeply nested callbacks.
๐ Syntax:
// Instead of: callback1(() => callback2(() => callback3()))
// Use: promise1().then(() => promise2()).then(() => promise3())
๐งช Example:
// โ Callback Hell
function callbackHell() {
getUser(123, function(err, user) {
if (err) throw err;
getUserPosts(user.id, function(err, posts) {
if (err) throw err;
getComments(posts[0], function(err, comments) {
if (err) throw err;
console.log(comments);
});
});
});
}
// โ
Promise Chain
function promiseChain() {
return getUser(123)
.then(user => getUserPosts(user.id))
.then(posts => getComments(posts[0]))
.then(comments => console.log(comments))
.catch(error => console.error('Error:', error));
}
// Promise-based functions
function getUser(id) {
return Promise.resolve({ id, name: 'Alice' });
}
function getUserPosts(userId) {
return Promise.resolve([{ id: 1, title: 'Post 1' }]);
}
function getComments(post) {
return Promise.resolve(['Great post!', 'Thanks for sharing']);
}
๐ Intro to Ajax, HTTP Request
โ Definition:
AJAX (Asynchronous JavaScript and XML) is a technique for making asynchronous HTTP requests to servers without refreshing the entire web page. HTTP requests allow web applications to communicate with servers to send and receive data in various formats like JSON, XML, or plain text. Modern JavaScript uses the Fetch API or libraries like Axios to make these requests more efficiently than the older XMLHttpRequest.
๐ Syntax:
// Traditional XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', 'url');
xhr.send();
// Modern Fetch API
fetch('url').then(response => response.json());
๐งช Example:
// Traditional AJAX with XMLHttpRequest
function makeAjaxRequest() {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log('AJAX Response:', data);
}
};
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1');
xhr.send();
}
// Modern approach with Fetch
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log('Fetch Response:', data))
.catch(error => console.error('Error:', error));
๐ Fetch API
โ Definition:
The Fetch API is a modern, promise-based interface for making HTTP requests in JavaScript, replacing the older XMLHttpRequest. It provides a cleaner, more flexible way to fetch resources from servers with built-in promise support. Fetch returns a promise that resolves to the Response object representing the response to the request.
๐ Syntax:
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
๐งช Example:
// GET Request
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
console.log('Status:', response.status);
return response.json();
})
.then(data => {
console.log('Post Title:', data.title);
console.log('Post Body:', data.body);
})
.catch(error => console.error('Error:', error));
// POST Request
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'My New Post',
body: 'This is the content of my post',
userId: 1
})
})
.then(response => response.json())
.then(data => console.log('Created Post:', data));
โ ๏ธ Error Handling in Fetch API
โ Definition:
Error handling in Fetch API requires checking both network errors and HTTP status codes since fetch only rejects for network failures, not HTTP error statuses. You must manually check response.ok or response.status to handle HTTP errors like 404 or 500. Proper error handling ensures your application gracefully handles both network issues and server-side errors.
๐ Syntax:
fetch(url)
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.catch(error => console.error('Error:', error));
๐งช Example:
fetch('https://jsonplaceholder.typicode.com/posts/999') // Non-existent post
.then(response => {
console.log('Response status:', response.status);
if (response.status >= 200 && response.status < 300) {
return response.json();
} else {
throw new Error(`HTTP Error: ${response.status}`);
}
})
.then(data => {
console.log('Data:', data);
})
.catch(error => {
console.error('Caught error:', error.message);
// Handle error (show user message, retry, etc.)
});
// Alternative with response.ok
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
๐ก Axios API
โ Definition:
Axios is a popular JavaScript library that provides a promise-based HTTP client for making API requests with more features than the native Fetch API. It automatically handles JSON parsing, provides better error handling, supports request/response interceptors, and has built-in timeout support. Axios works in both browsers and Node.js environments with a consistent API.
๐ Syntax:
// GET request
axios.get(url).then(response => console.log(response.data));
// POST request
axios.post(url, data).then(response => console.log(response.data));
๐งช Example:
// GET Request with Axios
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
console.log('Status:', response.status);
console.log('Data:', response.data);
console.log('Headers:', response.headers);
})
.catch(error => {
console.error('Axios Error:', error.message);
if (error.response) {
console.error('Error Status:', error.response.status);
}
});
// POST Request with Axios
axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'New Post with Axios',
body: 'Content created using Axios library',
userId: 1
})
.then(response => {
console.log('Created:', response.data);
})
.catch(error => {
console.error('Error creating post:', error);
});
// Axios with configuration
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts',
timeout: 5000,
headers: {
'Accept': 'application/json'
}
})
.then(response => console.log(`Found ${response.data.length} posts`));
๐ฏ Consume Promises with Async and Await
โ Definition:
Async/await is syntactic sugar built on top of promises that allows you to write asynchronous code that looks and behaves more like synchronous code. The 'async' keyword makes a function return a promise, while 'await' pauses the function execution until the promise resolves. This approach eliminates promise chains and makes error handling cleaner with try/catch blocks.
๐ Syntax:
async function functionName() {
try {
const result = await promiseFunction();
return result;
} catch (error) {
console.error(error);
}
}
๐งช Example:
// Using Promises (traditional way)
function fetchWithPromises() {
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => {
console.log('Promise way:', data.title);
return fetch('https://jsonplaceholder.typicode.com/posts/2');
})
.then(response => response.json())
.then(data => console.log('Second post:', data.title))
.catch(error => console.error('Error:', error));
}
// Using Async/Await (modern way)
async function fetchWithAsync() {
try {
const response1 = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data1 = await response1.json();
console.log('Async way:', data1.title);
const response2 = await fetch('https://jsonplaceholder.typicode.com/posts/2');
const data2 = await response2.json();
console.log('Second post:', data2.title);
} catch (error) {
console.error('Error:', error);
}
}
// Call the functions
fetchWithAsync();
๐พ Local Storage
โ Definition:
Local Storage is a web storage API that allows you to store data locally within a user's browser with no expiration time. The data persists even after the browser window is closed and can store up to 5-10MB of data depending on the browser. Local Storage only accepts string values, so objects must be converted using JSON.stringify() and JSON.parse().
๐ Syntax:
// Store data
localStorage.setItem('key', 'value');
// Retrieve data
const value = localStorage.getItem('key');
// Remove data
localStorage.removeItem('key');
๐งช Example:
// Store simple data
localStorage.setItem('username', 'Vitthal');
localStorage.setItem('theme', 'dark');
// Store complex data (object)
const userSettings = {
theme: 'dark',
language: 'en',
notifications: true
};
localStorage.setItem('settings', JSON.stringify(userSettings));
// Retrieve and use data
const username = localStorage.getItem('username');
console.log('Welcome back,', username); // Welcome back, Vitthal
const settings = JSON.parse(localStorage.getItem('settings'));
console.log('User theme:', settings.theme); // User theme: dark
// Check if data exists
if (localStorage.getItem('username')) {
console.log('User is logged in');
}
// Remove specific item
localStorage.removeItem('theme');
// Clear all localStorage
// localStorage.clear();
console.log('localStorage length:', localStorage.length);
๐ Session Storage
โ Definition:
Session Storage is similar to Local Storage but stores data only for the duration of a page session. The data is cleared when the tab is closed, making it suitable for temporary data that shouldn't persist across browser sessions. Like Local Storage, it only stores strings and has the same storage methods and capacity limitations.
๐ Syntax:
// Store data
sessionStorage.setItem('key', 'value');
// Retrieve data
const value = sessionStorage.getItem('key');
// Remove data
sessionStorage.removeItem('key');
๐งช Example:
// Store session data
sessionStorage.setItem('username', 'Vitthal');
sessionStorage.setItem('currentPage', 'dashboard');
// Store temporary user preferences
const tempSettings = {
sortBy: 'date',
viewMode: 'grid',
filters: ['active', 'recent']
};
sessionStorage.setItem('tempSettings', JSON.stringify(tempSettings));
// Retrieve session data
const username = sessionStorage.getItem('username');
console.log('Current user:', username); // Current user: Vitthal
const settings = JSON.parse(sessionStorage.getItem('tempSettings'));
console.log('Current sort:', settings.sortBy); // Current sort: date
// Get first key
const firstKey = sessionStorage.key(0);
console.log('First key:', firstKey);
// Check session storage length
console.log('Session items count:', sessionStorage.length);
// Remove specific item
sessionStorage.removeItem('currentPage');
// Clear all session storage
// sessionStorage.clear();
// Session storage will be automatically cleared when tab is closed
๐ Practical Example: Complete Data Fetching with Storage
๐งช Complete Example:
async function fetchAndStoreUserData() {
try {
// Check if data exists in localStorage
const cachedData = localStorage.getItem('userData');
if (cachedData) {
console.log('Using cached data:', JSON.parse(cachedData));
return JSON.parse(cachedData);
}
// Fetch new data if not cached
console.log('Fetching fresh data...');
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
// Store in localStorage for future use
localStorage.setItem('userData', JSON.stringify(userData));
// Store current session info
sessionStorage.setItem('lastFetch', new Date().toISOString());
console.log('Fresh data fetched and stored:', userData);
return userData;
} catch (error) {
console.error('Error fetching user data:', error);
// Try to use cached data as fallback
const fallbackData = localStorage.getItem('userData');
if (fallbackData) {
console.log('Using fallback cached data');
return JSON.parse(fallbackData);
}
throw error;
}
}
// Usage
fetchAndStoreUserData()
.then(user => {
console.log(`Hello ${user.name}! Email: ${user.email}`);
})
.catch(error => {
console.error('Failed to get user data:', error);
});
โ Summary
This comprehensive guide covers the evolution of asynchronous JavaScript from basic promises to modern async/await syntax, along with practical data storage solutions. Understanding these concepts is essential for building robust, user-friendly web applications that handle asynchronous operations efficiently while providing great user experiences through proper data management and error handling.
Subscribe to my newsletter
Read articles from Code Subtle directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Code Subtle
Code Subtle
At Code Subtle, we empower aspiring web developers through personalized mentorship and engaging learning resources. Our community bridges the gap between theory and practice, guiding students from basics to advanced concepts. We offer expert mentorship and write interactive, user-friendly articles on all aspects of web development. Join us to learn, grow, and build your future in tech!