⚡ TechVani: AI, Technology, Apps, Coding & Digital World
TechVaniAI • Technology • Apps • Coding • Digital World
HomeAI & TechAppsCodingFree AI ToolsStudent TechSecurityAboutContactPrivacy
TECHVANI // LEARN. BUILD. TEST.

Learn the tech. Build the idea.

Clear study notes, tested lab files and practical guides for coding, AI and digital safety.

Explore Latest →
šŸ¤– AI & TechAI news, tools & guides šŸ“± Apps & ToolsUseful apps & websites šŸ’» CodingProgramming & web development šŸŽ“ Student TechStudy & career technology šŸ” Cyber SecuritySafety & awareness

Latest Posts

Circuit Notebook theme

Exam Notes

šŸ“š BTech CSE Exam Notes

Find Semester 1-8 subjects in one place. Choose a semester and open the notes you need. This is a general study guide, not an official syllabus.

Open Exam Notes →🧪 Open Lab Files →

Exam Numericals & Study Series

Practice CPU scheduling and explore OS and DBMS topics in separate, easy-to-follow parts. New topics will appear in the hub as they are published.

Open Numericals Hub →

Start here and stay in touch

New to TechVani? Start with our free AI tools guide for students. It covers which tools to try and what to check before relying on an answer.

For step-by-step videos, visit SiteSudhar on YouTube.

Join TechVani on WhatsApp

Get new AI tools and coding guides in your WhatsApp Updates tab.

Browse Topics

Popular Posts

TechVani

JavaScript Fetch API for Beginners: Async/Await and Error Handling

JavaScript Fetch API: your first reliable request

Many beginner projects need data from a server: a weather report, a list of products, or a user's saved notes. JavaScript's fetch() function starts a network request and gives you a Promise for the eventual response. The page does not have to freeze while it waits. Here is a small pattern you can reuse, plus the common mistakes to avoid.

Start with async and await

An async function can pause at await while a Promise settles. The following example requests JSON from a sample endpoint and reports either the data or an error. Replace the URL with an endpoint you are allowed to use in your own project.

async function loadProducts() {
  try {
    const response = await fetch('https://example.com/api/products');
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    const products = await response.json();
    console.log(products);
  } catch (error) {
    console.error('Could not load products:', error);
  }
}
loadProducts();

The example.com address above is a placeholder, not a live products API. The important sequence is request, check response status, parse the body, and handle errors. You can paste this pattern into a real app after changing the endpoint.

Why check response.ok?

A common surprise is that fetch() does not reject its Promise merely because the server returns a status such as 404 or 500. It resolves with a Response object. The response.ok property is true for successful 2xx responses. If it is false, your code should decide what to do before parsing the body as if the request succeeded. A network failure, by contrast, can reject and reach catch directly.

Why await response.json() too?

The body is not parsed instantly. response.json() returns another Promise, so await it. If the server returns HTML or malformed JSON when you expected JSON, parsing can fail; catch handles that too. Do not assume every API error response has the same JSON shape as a success response.

Three mistakes students often make

1. Logging too soon: console.log(fetch(url)) prints a Promise, not the downloaded data. Await the result inside an async function.

2. Ignoring HTTP status: a 404 can pass through to your next line unless you inspect response.ok or response.status.

3. Hiding failures: an empty catch block can make a broken screen look like an empty database. Show a useful message and log enough context during development, without leaking tokens or private data to the console.

For an interview-prep app, you might call GET /api/questions and show a loading state, then a list of questions. If the call fails, offer a retry button instead of showing a blank page. When submitting an answer with POST, also check the server's response and avoid double-submitting while the request is in flight. These details make a small project feel much more dependable.

A short practice exercise

Create a small page with a Load button, a status line, and an empty list. On click, set the status to Loading, call a public demo API or your own local endpoint, check response.ok, await response.json(), then render the items. Test three paths: success, an invalid URL returning 404, and offline mode. Can a reader tell what happened in each case? If so, you have learned more than a syntax trick: you have learned to design the whole request lifecycle.

Read the original documentation

MDN, How to use promises: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Async_JS/Promises

MDN, Using the Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Related on TechVaani: AI Agents and the role of tools, permissions and feedback: https://techvaniai.blogspot.com/2026/09/ai-agents-what-they-are-how-they-work.html

Handle HTTP errors, POST requests and timeouts

I tested the three points below against a small local server using Node.js 22, so the outputs are real.

1. A 404 does not throw an error

fetch() can reject on network/request failures or cancellation, but not merely because the server returns 404 or 500. In the local lab below, raw 404 resolves with ok = false and status = 404.

2. Send data with POST

const res = await fetch('/api/items', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Notebook' })
});
const data = await res.json();

The test server replied with status 200 and the JSON I expected. Set Content-Type: application/json for a JSON body and stringify the object first. Do not copy that header to FormData uploads: the browser needs to set its boundary.

3. Stop a request that takes too long

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);
try {
  const res = await fetch(url, { signal: controller.signal });
} catch (err) {
  if (err.name === 'AbortError') console.log('Timed out');
} finally {
  clearTimeout(timer);
}

In my test the server waited 2 seconds, I aborted after 0.3 seconds, and the error name was AbortError.

Common mistakes

  • Forgetting await, so you get a Promise instead of data.
  • Calling response.json() twice. The body can be read only once.
  • Showing a blank page on failure. Show the user a short message.

Check yourself

Q1. The server returns 500. Does the catch block run?

Answer: A raw fetch does not reject merely for HTTP 500. Our checked helper throws on !response.ok, so its surrounding catch does run. Body parsing can also fail independently.

Q2. Which tool lets you cancel a fetch request?

Answer: AbortController with its signal.

More JavaScript: localStorage vs sessionStorage.

Request lifecycle: two awaits, different failures

Call fetch → await Response
Request/network/cancellation failure → catch
Response received → check status / ok
HTTP failure → throw or handle explicitly
Successful response → await body parsing
Malformed JSON → catch
Parsed data → validate shape → render safely

fetch resolves when response headers are available, not when JSON is already parsed. response.ok means HTTP 200-299, not that the body has the schema your app expects. A 204 response has no body; the helper in this lab returns null instead of trying JSON parsing. Adapt that choice to your own API contract.

A complete local lab you can run

Save this as fetch-lab.cjs and run node fetch-lab.cjs with Node.js 22. It creates its own server on an available loopback port, makes only local requests, and closes the server in finally. No API token or public service is needed. Node.js 22.23.3 was used for this run. This tests Fetch/Response behavior, not browser CORS enforcement.

// Node.js 22 local-only lab. No public API or credentials needed.
const http = require('node:http');
const assert = require('node:assert/strict');
async function getJSON(url, options = {}) {
  const response = await fetch(url, options);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  if (response.status === 204) return null;
  return response.json();
}
const server = http.createServer((req, res) => {
  if (req.url === '/missing') {
    res.writeHead(404); return res.end('Not found');
  }
  if (req.url === '/empty') {
    res.writeHead(204); return res.end();
  }
  if (req.url === '/bad-json') {
    res.setHeader('Content-Type', 'application/json');
    return res.end('{broken');
  }
  if (req.url === '/slow') {
    const timer = setTimeout(() => res.end('{}'), 2000);
    res.on('close', () => clearTimeout(timer)); return;
  }
  if (req.method === 'POST' && req.url === '/items') {
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify({name: JSON.parse(body).name}));
    }); return;
  }
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify([{id: 1, name: 'Notebook'}]));
});
(async () => {
  await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
  const base = `http://127.0.0.1:${server.address().port}`;
  try {
    const items = await getJSON(base + '/items');
    assert.equal(items[0].name, 'Notebook');
    console.log('GET:', items[0].name);
    const missing = await fetch(base + '/missing');
    assert.equal(missing.status, 404); assert.equal(missing.ok, false);
    console.log('raw 404:', missing.status, missing.ok);
    try { await getJSON(base + '/missing'); assert.fail('Expected HTTP error'); }
    catch (e) { assert.equal(e.message, 'HTTP 404'); console.log('checked:', e.message); }
    try { await getJSON(base + '/bad-json'); assert.fail('Expected JSON error'); }
    catch (e) { assert.equal(e.name, 'SyntaxError'); console.log('bad JSON:', e.name); }
    assert.equal(await getJSON(base + '/empty'), null);
    console.log('204:', 'null');
    const saved = await getJSON(base + '/items', {
      method: 'POST', headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({name: 'Notebook'})
    });
    assert.equal(saved.name, 'Notebook'); console.log('POST:', saved.name);
    const once = await fetch(base + '/items');
    await once.json();
    try { await once.json(); assert.fail('Expected consumed-body error'); }
    catch (e) { assert.equal(e.name, 'TypeError'); console.log('second read:', e.name); }
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 300);
    try { await getJSON(base + '/slow', {signal: controller.signal}); assert.fail('Expected abort'); }
    catch (e) { assert.equal(e.name, 'AbortError'); console.log('abort:', e.name); }
    finally { clearTimeout(timer); }
  } finally {
    server.closeAllConnections();
    await new Promise(resolve => server.close(resolve));
  }
})().catch(e => { console.error(e); process.exitCode = 1; });

Tested output:

GET: Notebook
raw 404: 404 false
checked: HTTP 404
bad JSON: SyntaxError
204: null
POST: Notebook
second read: TypeError
abort: AbortError

The abort timer is 300 ms and the slow endpoint waits 2000 ms, so it is deliberately cancelled first. The earlier browser timeout pattern uses 3000 ms; that is a different chosen limit, not the test's delay. Aborting the client request is not a rollback: a server may already have processed a submitted write.

Browser integration and security checklist

CaseWhat to do
Cross-origin APILet the server grant CORS. mode: no-cors gives an opaque response, not readable JSON; it is not a bypass.
Cookie-authenticated endpointCredentials default to same-origin. Cross-origin include needs server consent and CSRF protection; do not turn it on blindly.
Secret API keyKeep privileged keys on the server, not in browser source or localStorage.
Untrusted returned textRender text with textContent, not unsanitized innerHTML. Validate the expected data shape.
Repeated JSON readA consumed response body cannot be read again. If two consumers are necessary, clone before the first read and consider memory cost.
Retries / POSTDo not automatically replay writes unless the API has a safe idempotency design. Prevent accidental duplicate submits.
Parallel Load clicksDisable duplicate loads or cancel/ignore stale results so an old response cannot overwrite newer data.
Upload FormDataDo not manually force application/json or a multipart boundary; let the browser supply the FormData Content-Type.

Exam and interview quick answers

  1. Why two awaits? One obtains the Response; the other reads/parses its body.
  2. Is HTTP 404 a rejected fetch Promise? Not merely from the status. The lab's raw fetch resolves, while the checked helper throws HTTP 404.
  3. Can HTTP 200 still fail? Yes: invalid JSON, wrong application schema, or a later body-read failure.
  4. Is catch only for offline mode? No. It can also receive your own thrown HTTP error, cancellation or JSON parse failures.
  5. What happens for 204? Successful HTTP response with no body; don't unconditionally call json().
  6. Can you read JSON twice? Not from the same consumed Response body. Clone first if needed.
  7. Does no-cors repair a blocked API? No. An opaque response hides its body/headers; fix server-side CORS or use an authorized backend.
  8. Does abort undo a POST? No such guarantee. Confirm server state before deciding whether to retry.

Grounding: MDN Using Fetch, Response.ok, and Response and body methods. The local lab is reproducible; the sample browser endpoint near the top remains a clearly labelled placeholder.

Security references: OWASP XSS prevention for safe text sinks; OWASP browser storage guidance for keeping sensitive data off client storage.

Comments