asynchronous programming
HTTP request
web development
programming tutorials
asynchronous vs synchronous

Doing an asynchronous HTTP request - what's the difference between these two?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When two HTTP request examples both look asynchronous, the important difference is usually not the network call itself. The real distinction is how completion is represented, when later code continues, and how errors move through the program.

Both can be asynchronous at the transport level

An HTTP request is asynchronous when the program starts the request and continues running other code while the response is in flight. In JavaScript, both promise-based and async and await code can do that.

For example, these both start an asynchronous request:

javascript
fetch("/api/users/42")
  .then((response) => response.json())
  .then((data) => console.log(data));
javascript
1async function loadUser() {
2  const response = await fetch("/api/users/42");
3  const data = await response.json();
4  console.log(data);
5}

The network activity is still non-blocking in both versions. The difference is the style of control flow, not whether the socket suddenly behaves differently.

Promise chaining versus async and await

A promise-based version returns a promise to the caller:

javascript
1function loadUser(id) {
2  return fetch(`/api/users/${id}`)
3    .then((response) => {
4      if (!response.ok) {
5        throw new Error(`HTTP ${response.status}`);
6      }
7      return response.json();
8    });
9}
10
11loadUser(42).then((user) => console.log(user));

An async function also returns a promise, but it lets you write the steps in a top-to-bottom style:

javascript
1async function loadUserAsync(id) {
2  const response = await fetch(`/api/users/${id}`);
3  if (!response.ok) {
4    throw new Error(`HTTP ${response.status}`);
5  }
6  return await response.json();
7}
8
9const user = await loadUserAsync(42);
10console.log(user);

Under the hood, both versions are asynchronous. The main benefit of async and await is readability, especially when several dependent requests must happen in sequence.

Fire-and-forget versus awaited completion

Another major difference between two async request styles is whether the caller actually waits for the result.

Fire-and-forget:

javascript
1fetch("/api/log", {
2  method: "POST",
3  body: JSON.stringify({ event: "clicked" }),
4  headers: { "Content-Type": "application/json" }
5});
6
7console.log("Request started");

Awaited:

javascript
1await fetch("/api/log", {
2  method: "POST",
3  body: JSON.stringify({ event: "clicked" }),
4  headers: { "Content-Type": "application/json" }
5});
6
7console.log("Request finished");

Both requests are asynchronous, but the second form makes later code depend on successful completion. That is a real behavioral difference even though neither one blocks the JavaScript event loop the way a synchronous network API would.

Error propagation is often the real difference

Different async styles can make errors feel very different.

Promise chain:

javascript
loadUser(42)
  .then((user) => console.log(user))
  .catch((err) => console.error("Request failed:", err));

async and await:

javascript
1try {
2  const user = await loadUserAsync(42);
3  console.log(user);
4} catch (err) {
5  console.error("Request failed:", err);
6}

This matters because people often compare two snippets and think the syntax is the whole story. In practice, how each version handles failure, retries, cleanup, and dependent work is often the more important difference.

Sequential versus concurrent async requests

Two HTTP request patterns may both be asynchronous but still differ in whether they run sequentially or concurrently.

Sequential:

javascript
const user = await fetch("/api/user/42").then((r) => r.json());
const posts = await fetch(`/api/posts?user=${user.id}`).then((r) => r.json());

Concurrent:

javascript
1const [user, settings] = await Promise.all([
2  fetch("/api/user/42").then((r) => r.json()),
3  fetch("/api/settings/42").then((r) => r.json())
4]);

Both use asynchronous HTTP requests, but the second version allows both requests to progress at the same time. If the data does not depend on one another, concurrency is often the better design.

A request can be asynchronous while your code still behaves badly

One source of confusion is that using an asynchronous API does not automatically produce good async structure. For example, if you start a request but forget to return or await the promise, outer code may continue too early.

javascript
1async function broken() {
2  fetch("/api/data");
3  return "done";
4}

This function returns before the request result is used. The request is still asynchronous, but the program logic is wrong.

Correct version:

javascript
1async function correct() {
2  const response = await fetch("/api/data");
3  return await response.json();
4}

Common Pitfalls

The biggest mistake is thinking await makes an HTTP request synchronous. It does not. It only pauses that async function while the event loop continues handling other work.

Another issue is forgetting that parsing the body can also be asynchronous. In the Fetch API, response.json() returns a promise too, so awaiting the request alone is not enough if you still need the parsed payload.

Developers also start requests without returning or awaiting the promise when later code depends on the result. That creates race conditions that can look like "async is broken" when the real problem is control flow.

Finally, do not compare two request snippets only by syntax. Check whether they differ in sequencing, concurrency, error propagation, or whether the caller actually waits for completion.

Summary

  • Two HTTP request snippets can both be asynchronous while still behaving differently.
  • Promise chains and async and await mainly differ in how completion and errors are expressed.
  • Fire-and-forget and awaited requests have different downstream behavior.
  • Sequential async code and concurrent async code are not the same thing.
  • The important comparison is usually control flow and error handling, not whether the HTTP transport is "really async."

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.