Use modern JavaScript to fetch and work with data from REST APIs without freezing your application.
Synchronous code runs line by line, blocking execution. Asynchronous code lets other work happen while waiting.
Each task waits for the previous one to complete. UI is blocked.
Tasks run in the background. App stays responsive.
Async JavaScript powers real business dashboards, metrics loading, and responsive user actions.
async function loadSales() {
try {
const response = await fetch('https://api.example.com/sales');
if (!response.ok) throw new Error('API error');
const data = await response.json();
renderTable(data);
} catch (err) {
console.error('Failed to load sales:', err.message);
showError('Unable to load data. Please try again.');
}
}
Get data from APIs to build charts, KPIs, and reports.
Handle delays gracefully while waiting for server responses.
Users can interact with the app while data is loading.
A Promise represents the eventual result of an asynchronous operation: pending, fulfilled, or rejected.
The operation is in progress. Result is not yet ready.
Operation completed successfully. Triggers attached .then().
Operation failed for some reason. Triggers attached .catch().
.then(), .catch(), .finally()
Control the flow of resolved data, intercept errors safely, and always run required cleanup tasks.
Gets the result when Promise is fulfilled. Good for processing data and continuing flow.
Catches errors when Promise is rejected (network errors, server errors, invalid payloads).
Runs code regardless of success or failure. Good for hiding loading spinners and resetting UI.
fetch('/api/users')
.then(res => res.json()) // Parse JSON
.then(users => users.filter(u => u.active)) // Process filtered data
.catch(err => console.error(err)) // Handle any error in chain
.finally(() => console.log('All done!')); // Always run cleanup
Promise.all()
Use sequential chaining when the second call requires data from the first; run independent calls in parallel.
Use results from one call in the next: Get User → Get Orders → Show Orders.
Load multiple independent datasets simultaneously to reduce loading time.
Write asynchronous code that looks and reads like standard synchronous code, without callback pyramids.
async keyword.await pauses this function until the Promise resolves.Combine multiple API results or execute dependent requests cleanly using async / await.
async function loadDashboard() {
try {
const summary = await fetch('/api/summary').then(r => r.json());
const schools = await fetch('/api/schools').then(r => r.json());
const trends = await fetch('/api/trends').then(r => r.json());
return { summary, schools, trends };
} catch (error) {
console.error('Failed to load dashboard data:', error);
showError('Unable to load data. Please try again.');
return null;
}
}
GET /api/summaryGET /api/schoolsGET /api/trends
fetch() is the modern built-in browser API to communicate with web services and REST endpoints.
Specify the URL resource endpoint to read or write data.
Configure HTTP method, body payload, and credentials.
Specify metadata like Content-Type and auth tokens.
res.ok
fetch() only rejects on network failures! You MUST manually check response.ok for HTTP 4xx/5xx errors.
Verify res.ok === true (HTTP status 200–299) before reading body.
await res.json() streams and converts JSON text into a JavaScript object.
Throw descriptive errors when !res.ok to trigger your catch handler.
async function api(url, options = {}) {
const res = await fetch(url, options);
// CRITICAL: Manual status check!
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status} ${res.statusText}`);
}
return await res.json();
}
Understand how APIs exchange structured data for applications, automation, and analytics.
What your app sends to an API:
What the API sends back:
Convert and validate structured data:
const data = await res.json();JSON.stringify(payload);Map database and business operations directly to standard HTTP verbs.
// Response JSON
{
"data": [
{ "id": 1, "name": "Laptop", "price": 25900 },
{ "id": 2, "name": "Mouse", "price": 450 }
],
"total": 2
}
// Request Payload JSON
{
"name": "Keyboard",
"price": 1290,
"category": "Accessories"
}
// Status: 201 Created
// PUT: Update record
body: JSON.stringify({ price: 28900 })
// DELETE: Remove record
await fetch('/api/products/1', {
method: 'DELETE'
});
Follow this 4-step request flow with validation, error isolation, and UI state preservation.
async function loadKpis() {
setLoading(true); clearError(); // 1. Start loading state
try {
const res = await fetch('/api/kpis', { headers: { 'Accept': 'application/json' } }); // 2. Send request
if (!res.ok) throw new Error(res.statusText);
const json = await res.json(); // 3. Parse JSON
if (!Array.isArray(json.records)) throw new Error('Invalid data format'); // Validate data
const kpis = json.records.map(r => ({ id: r.id, label: r.name, value: Number(r.value) }));
setKpis(kpis); setLastUpdated(new Date()); // Update UI
} catch (err) {
handleError(err); // Preserve usable state! Don't clear previous KPI data on failure
} finally {
setLoading(false); // 4. Always end loading state in finally!
}
}
Follow these essential rules to build resilient, reliable, and user-friendly API integrations.
async / await for readability and error boundaries.try / catch / finally for safe error handling and UI reset.res.ok before calling res.json().Array.isArray()) before rendering.await, resulting in unhandled Promise objects.fetch() rejects on HTTP 404 or 500 errors.