asynchronous requests
concurrency
programming tutorial
javascript promises
async/await

How to perform multiple asynchronous requests starting one after another

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

If multiple asynchronous requests must start one after another, you do not want parallel execution. You want sequencing. In JavaScript, the clearest solution is usually a for...of loop with await, because each request begins only after the previous one has completed.

Sequential Requests with async and await

Here is the most direct pattern:

javascript
1const urls = [
2  "https://api.example.com/a",
3  "https://api.example.com/b",
4  "https://api.example.com/c"
5];
6
7async function fetchSequentially() {
8  const results = [];
9
10  for (const url of urls) {
11    const response = await fetch(url);
12    if (!response.ok) {
13      throw new Error(`Request failed: ${response.status}`);
14    }
15
16    results.push(await response.json());
17  }
18
19  return results;
20}

This starts request b only after request a is fully resolved, and request c only after b finishes.

That is the right behavior when:

  • order matters
  • later requests depend on earlier results
  • you must avoid overlapping load on a downstream service

Use Prior Results in the Next Request

Sequential execution becomes especially important when each step depends on the last step’s output.

javascript
1async function fetchDependentSequence() {
2  const user = await fetch("/api/user/42").then(r => r.json());
3  const orders = await fetch(`/api/orders?user=${user.id}`).then(r => r.json());
4  const invoice = await fetch(`/api/invoice/${orders[0].invoiceId}`).then(r => r.json());
5
6  return { user, orders, invoice };
7}

This is not just a style choice. These requests cannot run correctly in parallel because the later URLs are not even known yet.

Promise Chaining Works Too

Before async and await, the usual solution was promise chaining.

javascript
1fetch("/api/a")
2  .then(response => response.json())
3  .then(dataA => fetch(`/api/b?id=${dataA.id}`))
4  .then(response => response.json())
5  .then(dataB => fetch(`/api/c?id=${dataB.id}`))
6  .then(response => response.json())
7  .then(dataC => {
8    console.log(dataC);
9  })
10  .catch(error => {
11    console.error(error);
12  });

This still works, but async and await is usually easier to read and maintain for multi-step flows.

Process an Array Sequentially

If you already have an array of tasks, loop through them rather than using Promise.all.

javascript
1async function fetchManySequentially(urls) {
2  const results = [];
3
4  for (const url of urls) {
5    const response = await fetch(url);
6    results.push(await response.text());
7  }
8
9  return results;
10}

Do not use Promise.all here if the requirement is "one after another," because Promise.all starts everything immediately.

Avoid forEach with async

This is a common mistake:

javascript
1urls.forEach(async (url) => {
2  const response = await fetch(url);
3  console.log(await response.text());
4});

forEach does not wait for each callback the way people often expect. The requests begin independently, so the code is not truly sequential.

If you need real sequencing, use for...of.

Add Error Handling and Timeouts

Sequential flows still need operational safeguards. One slow or broken request can stall the whole chain.

javascript
1async function fetchWithTimeout(url, ms = 5000) {
2  const controller = new AbortController();
3  const timer = setTimeout(() => controller.abort(), ms);
4
5  try {
6    const response = await fetch(url, { signal: controller.signal });
7    if (!response.ok) {
8      throw new Error(`HTTP ${response.status}`);
9    }
10    return response.json();
11  } finally {
12    clearTimeout(timer);
13  }
14}

Then use it in the sequential loop so each step has a bounded wait time.

Common Pitfalls

The most common mistake is using Promise.all or forEach when the requirement is actually sequential execution. Another is forgetting that one failed request can stop the whole chain unless you handle errors deliberately. Developers also sequence requests by habit when they could safely run them in parallel, which hurts performance for no reason. The right question is not "can I await in a loop" but "does the problem require sequence or concurrency."

Summary

  • Use for...of with await when requests must start one after another.
  • Use sequential flow when later requests depend on earlier results or when order matters.
  • Promise chaining also works, but async and await is usually clearer.
  • Avoid forEach for sequencing asynchronous work.
  • Add timeouts and error handling so one bad request does not stall the entire workflow silently.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Basis Lab
No course covers this one yet

Basis Lab writes one for you from a sentence about what you want to be able to do, then teaches it and checks you understood. Your first course is free.

Build my course
Track what you have practised

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

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions