Async/Await
async/await is syntactic sugar over Promises that makes asynchronous code look like synchronous code. A function declared with async always returns a Promise. await can only be used inside an async function.
Code Example
Async/Await Basics
// An async function — always returns a Promise
async function fetchData() {
const response = await fetch('https://api.university.edu/student');
const data = await response.json();
return data;
}
// Using it
fetchData().then(data => console.log(data));
Error Handling with try...catch
async function fetchStudent(studentId) {
try {
const response = await fetch(`/api/student/${studentId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch data:', error.message);
// You can re-throw or return a default
return null;
}
}
Await with a Promise
async function processData() {
// await on a Promise — waits until it resolves
const data = await new Promise((resolve) => {
setTimeout(() => resolve('Data from Promise'), 1000);
});
console.log(data); // "Data from Promise"
return data;
}
Code Explanation
Parallel vs Sequential
// SEQUENTIAL — one by one (slow)
async function sequential() {
console.time('sequential');
const student = await fetch('/api/student').then(r => r.json());
const dsn = await fetch('/api/lecturer').then(r => r.json());
const mk = await fetch('/api/course').then(r => r.json());
console.timeEnd('sequential'); // ~3 seconds if each takes 1 second
return { student, dsn, mk };
}
// PARALLEL — at the same time (fast)
async function parallel() {
console.time('parallel');
const [student, dsn, mk] = await Promise.all([
fetch('/api/student').then(r => r.json()),
fetch('/api/lecturer').then(r => r.json()),
fetch('/api/course').then(r => r.json())
]);
console.timeEnd('parallel'); // ~1 second because they run concurrently
return { student, dsn, mk };
}
Top-Level Await
// In a module (type="module" on the script tag)
// Await can be used directly at the top level without wrapping in an async function
const response = await fetch('/api/student');
const data = await response.json();
console.log(data);
// Useful for initial application setup
const config = await fetch('/api/config').then(r => r.json());
const user = await fetch('/api/user/me').then(r => r.json());
renderApp(config, user);
For...of with Await
// Sequential processing with a loop
async function processFiles(fileList) {
for (const file of fileList) {
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: file
});
console.log(`${file.name} uploaded successfully`);
} catch (error) {
console.error(`Failed to upload ${file.name}:`, error);
}
}
}
Real-World Examples
Sequential API Calls: Login → Profile → Courses
async function loginDanLoadDashboard(email, password) {
try {
// Step 1: Login
console.log('Sedang login...');
const loginRes = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!loginRes.ok) {
throw new Error('Email or password incorrect');
}
const { token } = await loginRes.json();
localStorage.setItem('authToken', token);
// Step 2: Fetch the profile
console.log('Loading profile...');
const profileRes = await fetch('/api/user/profile', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!profileRes.ok) throw new Error('Failed to load profile');
const profile = await profileRes.json();
renderProfile(profile);
// Step 3: Fetch the courses
console.log('Loading course...');
const coursesRes = await fetch(`/api/courses?student=${profile.studentId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!coursesRes.ok) throw new Error('Failed to load courses');
const courses = await coursesRes.json();
renderCourses(courses);
// Everything succeeded
showNotification('Welcome, ' + profile.name + '!', 'success');
} catch (error) {
showNotification(error.message, 'error');
// Redirect to login if the session expired
if (error.message.includes('401')) {
window.location.href = '/login';
}
}
}
Parallel Fetch for a Dashboard
async function loadDashboardData() {
const token = localStorage.getItem('authToken');
const headers = { 'Authorization': `Bearer ${token}` };
// Fetch all data in parallel
const [profileRes, statsRes, jadwalRes, notifRes] = await Promise.all([
fetch('/api/user/profile', { headers }),
fetch('/api/statistics/student', { headers }),
fetch('/api/schedule/this-week', { headers }),
fetch('/api/notifikasi?unreadOnly=true', { headers })
]);
// Parse all the JSON in parallel
const [profile, stats, schedule, notifikasi] = await Promise.all([
profileRes.json(),
statsRes.json(),
jadwalRes.json(),
notifRes.json()
]);
// Render all components
renderProfile(profile);
renderStatistics(stats);
renderJadwal(schedule);
renderNotifikasi(notifikasi);
}
Robust Error Handling
async function fetchDataWithFallback() {
try {
// Try the primary server
const response = await fetch('https://api.university.edu/data');
if (!response.ok) throw new Error('Main server error');
return await response.json();
} catch (primaryError) {
console.warn('Primary server failed, trying mirror...');
try {
// Fall back to the mirror
const response = await fetch('https://api-mirror.university.edu/data');
if (!response.ok) throw new Error('Mirror error');
return await response.json();
} catch (mirrorError) {
console.warn('Mirror also failed, using cache...');
// Fall back to the cache
const cached = localStorage.getItem('cachedData');
if (cached) return JSON.parse(cached);
throw new Error('All data sources failed');
}
}
}
Upload with Progress (Using ReadableStream)
async function uploadWithProgress(file, onProgress) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
// Note: real-time progress tracking requires XMLHttpRequest
// or the Streaming API for fetch
});
if (!response.ok) throw new Error('Upload failed');
return response.json();
}
Async vs Promise
// With Promise chains
function loadData() {
return fetch('/api/data')
.then(r => r.json())
.then(data => {
return fetch(`/api/detail/${data.id}`);
})
.then(r => r.json())
.then(detail => {
render(detail);
})
.catch(err => {
console.error(err);
});
}
// With async/await — easier to read
async function loadData() {
try {
const dataRes = await fetch('/api/data');
const data = await dataRes.json();
const detailRes = await fetch(`/api/detail/${data.id}`);
const detail = await detailRes.json();
render(detail);
} catch (err) {
console.error(err);
}
}
Conclusion
async/await is the cleanest way to write asynchronous JavaScript. Use try/catch for error handling, Promise.all() for parallel operations, and top-level await for module initialization. Sequential awaits are right for dependent operations (login → profile → data), while parallel execution with Promise.all() is for independent operations.