JavaScript: Intermediate
Introduction
Asynchronous programming is essential for JavaScript. This article covers callbacks, promises, async/await, the Fetch API, and error handling patterns.
Synchronous vs Asynchronous
// Synchronous (blocks execution)
console.log('1');
console.log('2');
console.log('3');
// Output: 1, 2, 3
// Asynchronous (non-blocking)
console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');
// Output: 1, 3, 2 (after 1 second)Callbacks
// Callback function
function fetchData(callback) {
setTimeout(() => {
callback('Data loaded');
}, 1000);
}
fetchData((data) => console.log(data));
// Callback hell (nested callbacks)
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
console.log(comments);
});
});
});Promises
// Creating a promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) resolve('Success!');
else reject('Error!');
}, 1000);
});
// Consuming a promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
// Chaining promises
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
// Promise.all (wait for all)
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results));
// Promise.race (first to complete)
Promise.race([fastPromise, slowPromise])
.then(result => console.log(result));Async/Await
// Async function
async function fetchData() {
return 'Data';
}
// Equivalent to
function fetchData() {
return Promise.resolve('Data');
}
// Await (must be inside async)
async function getUserData() {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id);
return { user, posts };
}
// Error handling with try/catch
async function getData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}
// Parallel execution
async function getAllData() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return { users, posts, comments };
}Fetch API
// Basic GET request
async function fetchData() {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('HTTP error');
const data = await response.json();
return data;
}
// POST request
async function createUser(user) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
});
return response.json();
}
// With authentication
async function fetchProtectedData(token) {
const response = await fetch('/api/protected', {
headers: { 'Authorization': `Bearer ${token}` }
});
return response.json();
}
// Handle different response types
const text = await fetch('/file.txt').then(r => r.text());
const json = await fetch('/api/data').then(r => r.json());
const blob = await fetch('/image.png').then(r => r.blob());Error Handling
// Try/catch/finally
async function loadData() {
try {
const data = await fetchData();
return data;
} catch (error) {
console.error('Error:', error.message);
return null;
} finally {
console.log('Loading complete');
}
}
// Multiple error handling
async function robustFetch() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'TypeError') {
console.log('Network error');
} else if (error.name === 'SyntaxError') {
console.log('Invalid JSON');
} else {
console.log('Unknown error');
}
throw error;
}
}Practical Example: API Client
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: { 'Content-Type': 'application/json' },
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
get(endpoint) {
return this.request(endpoint);
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
// Usage
const api = new ApiClient('https://api.example.com');
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John' });Key Takeaways
- Use async/await for cleaner async code
- Always handle errors with try/catch
- Use Promise.all for parallel execution
- Check response.ok when using Fetch
- Use JSON.stringify for POST bodies