Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
⚡ Days 15–22 – Advanced & Async JavaScript  ·  Day 20

Day 20: async/await & fetch: Working with APIs

⏱ 110 min📶 Intermediate → Pro🎯 100% self-study
Day 20 / 30

✅ Today you will learn

  • async functions and await
  • Error handling with try/catch/finally
  • fetch: GET, response.ok, status codes, JSON
  • Loading, empty and error states in the UI
  • POST JSON, handle validation errors, timeouts and parallel requests

🏢 In the real world

Until now the products were typed into our JavaScript. Real shops load them from a server so the owner can change prices without a developer. We built a free practice API for this course at codingeasily.in/api/sweets/ – it returns real JSON, supports search, can be made slow (?delay=2000) or broken (?fail=1), and validates orders. Today you connect to it like a professional front-end developer.

async and await

JavaScript
const wait = (ms, value) => new Promise(r => setTimeout(() => r(value), ms));
const getUser = id => wait(50, { id, name: 'Aarti' });
const getOrders = user => wait(50, [{ id: 1042, total: 1800 }]);

// Same flow as yesterday’s promise chain – but reads top to bottom
async function showLastOrder(userId) {
  const user = await getUser(userId);         // pause here until the promise settles
  const orders = await getOrders(user);
  return `${user.name}: last order #${orders[0].id} (₹${orders[0].total})`;
}

showLastOrder(7).then(console.log);           // an async function ALWAYS returns a promise
console.log('This line runs first – await only pauses inside the async function');
This line runs first – await only pauses inside the async function Aarti: last order #1042 (₹1800)

Errors with try/catch/finally

JavaScript
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function payWithUpi(amount) {
  await wait(50);
  if (amount > 10000) throw new Error('UPI limit exceeded for this demo');
  return `₹${amount} paid via UPI ✅`;
}

async function checkout(amount) {
  console.log('⏳ Processing…');
  try {
    const receipt = await payWithUpi(amount);
    console.log(receipt);
  } catch (err) {
    console.log('❌ Payment failed:', err.message);
  } finally {
    console.log('Spinner hidden');
  }
}
(async () => { await checkout(1800); await checkout(25000); })();
⏳ Processing… ₹1800 paid via UPI ✅ Spinner hidden ⏳ Processing… ❌ Payment failed: UPI limit exceeded for this demo Spinner hidden
💡 Sequential vs parallel

Two awaits in a row run one after another. If the requests do not depend on each other, start them together: const [menu, offers] = await Promise.all([getMenu(), getOffers()]); – often twice as fast.

fetch: your first API call

HTML + JavaScript
<button id="load">Load menu from the API</button>
<pre id="out" style="max-height:220px;overflow:auto;background:#111827;color:#a7f3d0;padding:10px;border-radius:8px">Click the button…</pre>

<script>
const API = 'https://codingeasily.in/api/sweets/';

document.querySelector('#load').addEventListener('click', async () => {
  const out = document.querySelector('#out');
  out.textContent = 'Loading…';
  try {
    const response = await fetch(API);                  // 1. send the request
    if (!response.ok) throw new Error('HTTP ' + response.status);   // 2. check the status!
    const products = await response.json();              // 3. read the JSON body
    out.textContent = `Status ${response.status} · ${products.length} products\n\n` +
      products.map(p => `${p.id}. ${p.name.padEnd(18)} ₹${p.price}/${p.unit}  stock ${p.stock}`).join('\n');
  } catch (err) {
    out.textContent = 'Could not load the menu: ' + err.message;
  }
});
</script>
▶  A real HTTP request to the course API
StepCodeWhy
Sendconst res = await fetch(url)Resolves when headers arrive
Checkif (!res.ok) throw …fetch does NOT reject on 404/500 – only on network failure
Readawait res.json() / res.text()The body is read separately, also async
Handletry/catch around everythingOffline, timeouts, bad JSON, server errors
StatusMeaningShow the user
200 OK / 201 CreatedSuccessThe data / “Order placed”
400 Bad Request / 422Invalid inputField-level errors
401 / 403Not logged in / not allowedLogin prompt
404 Not FoundWrong URL or missing item“Product not found”
429 Too Many RequestsRate limited“Please wait and retry”
500 / 502 / 503Server problem“Something went wrong, try again” + retry button

Loading, empty, error and success states

HTML + JavaScript
<style>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin-top: 10px; }
.card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #fff; }
.card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: #f3f4f6; }
.card div { padding: 8px 10px; font-size: .9rem; }
.muted { color: #6b7280; }
.error { background: #fee2e2; color: #7f1d1d; padding: 10px; border-radius: 8px; }
.skeleton { height: 170px; border-radius: 12px; background: linear-gradient(90deg, #eee 25%, #f6f6f6 50%, #eee 75%); background-size: 200% 100%; animation: sh 1.2s linear infinite; }
@keyframes sh { to { background-position: -200% 0; } }
</style>

<input id="q" placeholder="Search (try kaju, box, xyz)" aria-label="Search">
<select id="cat" aria-label="Category"><option value="">All</option><option value="milk">Milk</option><option value="dry">Dry</option><option value="box">Gift boxes</option></select>
<label><input type="checkbox" id="slow"> Slow network</label>
<label><input type="checkbox" id="fail"> Server error</label>
<div id="view" aria-live="polite"></div>

<script>
const API = 'https://codingeasily.in/api/sweets/';
const view = document.querySelector('#view');

function showLoading() {
  view.innerHTML = '<div class="grid">' + '<div class="skeleton"></div>'.repeat(4) + '</div>';
}
function showError(message) {
  view.innerHTML = '';
  const box = document.createElement('div');
  box.className = 'error';
  box.textContent = '⚠️ ' + message + ' ';
  const retry = document.createElement('button');
  retry.textContent = 'Try again';
  retry.addEventListener('click', load);
  box.append(retry);
  view.append(box);
}
function showProducts(products) {
  if (products.length === 0) { view.innerHTML = '<p class="muted">No sweets match your search 🙁</p>'; return; }
  const grid = document.createElement('div');
  grid.className = 'grid';
  for (const p of products) {
    const card = document.createElement('article');
    card.className = 'card';
    card.innerHTML = '<img alt=""><div><b></b><br><span class="muted"></span></div>';
    card.querySelector('img').src = p.image;
    card.querySelector('b').textContent = p.name;
    card.querySelector('.muted').textContent = p.stock > 0 ? `₹${p.price}/${p.unit}` : 'Sold out';
    grid.append(card);
  }
  view.replaceChildren(grid);
}

async function load() {
  const params = new URLSearchParams();                       // builds ?q=kaju&category=dry safely
  const q = document.querySelector('#q').value.trim();
  const cat = document.querySelector('#cat').value;
  if (q) params.set('q', q);
  if (cat) params.set('category', cat);
  if (document.querySelector('#slow').checked) params.set('delay', 1500);
  if (document.querySelector('#fail').checked) params.set('fail', 1);

  showLoading();
  try {
    const res = await fetch(`${API}?${params}`);
    if (!res.ok) throw new Error(`The server had a problem (HTTP ${res.status}).`);
    showProducts(await res.json());
  } catch (err) {
    showError(err.message.startsWith('The server') ? err.message : 'You seem to be offline.');
  }
}
document.querySelectorAll('#q, #cat, #slow, #fail').forEach(el => el.addEventListener('change', load));
document.querySelector('#q').addEventListener('keyup', e => { if (e.key === 'Enter') load(); });
load();
</script>
▶  Tick “Slow network” or “Server error” and change the category to see every state
🧠 The four states every data screen needs

Loading (skeletons), empty (“no results”), error (with a retry button) and success. Most beginner apps only build success – then show a blank screen on slow 4G. Interviewers and users notice the difference.

Sending data: POST an order

HTML + JavaScript
<form id="order">
  <input name="name" placeholder="Name" value="Aarti">
  <input name="phone" placeholder="Mobile" value="98290">
  <button>Place order (2 kg Kaju Katli)</button>
</form>
<pre id="res" style="background:#f8fafc;padding:10px;border-radius:8px"></pre>

<script>
const API = 'https://codingeasily.in/api/sweets/';
document.querySelector('#order').addEventListener('submit', async (e) => {
  e.preventDefault();
  const btn = e.currentTarget.querySelector('button');
  const out = document.querySelector('#res');
  const form = Object.fromEntries(new FormData(e.currentTarget));
  btn.disabled = true;                                   // prevent double orders
  out.textContent = 'Sending…';
  try {
    const res = await fetch(API, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...form, items: [{ id: 1, qty: 2 }] }),
    });
    const data = await res.json();
    if (res.status === 422) {                            // server-side validation errors
      out.textContent = '⚠️ ' + data.error + '\n' + Object.entries(data.fields).map(([f, m]) => `• ${f}: ${m}`).join('\n');
    } else if (!res.ok) {
      throw new Error('HTTP ' + res.status);
    } else {
      out.textContent = `✅ ${data.message}\nOrder ${data.orderId} · total ₹${data.total} (delivery ₹${data.delivery})`;
    }
  } catch (err) {
    out.textContent = '❌ Could not send the order: ' + err.message;
  } finally {
    btn.disabled = false;
  }
});
</script>
▶  Submit with the short number (422 errors), then fix it to 9829012345 (201 Created)

Timeouts and cancelling with AbortController

JavaScript – a reusable fetchJSON helper
async function fetchJSON(url, { timeout = 8000, ...options } = {}) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);
  try {
    const res = await fetch(url, { ...options, signal: controller.signal });
    const data = await res.json().catch(() => null);          // body may be empty
    if (!res.ok) {
      const error = new Error(data?.error || `HTTP ${res.status}`);
      error.status = res.status;
      error.data = data;
      throw error;
    }
    return data;
  } catch (err) {
    if (err.name === 'AbortError') throw new Error('The server took too long. Please try again.');
    throw err;
  } finally {
    clearTimeout(timer);
  }
}

// usage
const products = await fetchJSON('https://codingeasily.in/api/sweets/?category=dry', { timeout: 5000 });
⚠️ CORS – “blocked by CORS policy”

Browsers only let a page read responses from another domain if that server allows it with an Access-Control-Allow-Origin header. Our practice API sends it, so it works from any page. If you see a CORS error with another API, it must be fixed on the server (or call it through your own backend) – not in front-end code.

🏅 Job skills unlocked today

✔ Write async functions with await and try/catch✔ Call APIs with fetch and check response.ok✔ Build loading, empty, error and success states✔ POST JSON, handle 422 errors, timeouts and parallel requests

Practice tasks

🏋️ Practice – do it yourself

Task 1: Load one product with ?id=2 and show its name and price; then try ?id=99 and show “Product not found” using the 404 status.
Show solution
▶  Result
Task 2: Load the milk sweets and the gift boxes in parallel with Promise.all and print both counts.
Task 3: Add a 3-second timeout to the product loader using AbortController and test it with ?delay=3000.

Quick quiz

🧠 Quick Quiz – test yourself

1. An async function always returns…
Even if you return a value.
2. fetch rejects on HTTP 500?
Network failures only.
3. Read a JSON body with…
Also async.
4. Send JSON in a POST:
Stringify.
5. Cancel a slow request:
controller.abort().

Interview corner

💼 Interview Corner

How do you handle errors with fetch?

Wrap in try/catch; network errors and aborts reject, but HTTP errors do not, so check response.ok/status and throw; parse error bodies for messages; show user-friendly states with retry; add timeouts via AbortController; log details for developers.

What is CORS?

Cross-Origin Resource Sharing: a browser security rule that blocks a page from reading responses from another origin unless the server allows it with Access-Control-Allow-* headers. Preflight OPTIONS requests are sent for non-simple requests such as JSON POSTs.

Summary

  • async/await = readable promises; wrap in try/catch/finally.
  • fetch → check res.ok → await res.json().
  • Always build loading, empty, error (+ retry) and success states.
  • POST JSON with headers; handle 422 field errors; use AbortController for timeouts.
➡️ Next

Day 21: modules, npm and modern tooling – organise code into files and use Vite like real teams.

Frequently Asked Questions

Can I use the practice API in my own projects?
Yes, for learning. It returns sample data, never stores orders and may change. For a real shop you need your own backend (e.g. PHP/MySQL or Node).
fetch or axios?
fetch is built in and enough for most apps, especially with a small helper like fetchJSON. axios adds conveniences (interceptors, automatic JSON) and is common in older projects.
← JavaScript (30 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 21: Modules, npm & Modern Tooling Your progress: 20 / 30 (67%) · Lesson 21 / 30 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free