JavaScript Tutorial menu

JavaScript Async/Await in Depth

You met promises and async/await in the async lesson. This lesson shows how to run many tasks together, handle their errors, and cancel them.

A Helper for the Examples

Real requests need a server, so these examples use a fake one. fakeRequest waits for some time, then succeeds or fails, just like a real network call.

Example

javascript
function fakeRequest(name, ms, fail = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (fail) reject(new Error(name + " failed"));
      else resolve(name + " loaded");
    }, ms);
  });
}

Sequential vs Parallel

If you await one task after another, each waits for the one before. If the tasks do not depend on each other, that wastes time.

Example

html
<!DOCTYPE html>
<html>
<body>

<p id="demo">Working...</p>

<script>
function fakeRequest(name, ms) {
  return new Promise(resolve => setTimeout(() => resolve(name), ms));
}

async function run() {
  const out = document.getElementById("demo");

  let t = performance.now();
  await fakeRequest("profile", 1000);
  await fakeRequest("courses", 1000);
  const slow = Math.round((performance.now() - t) / 1000);

  t = performance.now();
  await Promise.all([fakeRequest("profile", 1000), fakeRequest("courses", 1000)]);
  const fast = Math.round((performance.now() - t) / 1000);

  out.innerHTML = "One after another: about " + slow + " s<br>Together with Promise.all: about " + fast + " s";
}

run();
</script>

</body>
</html>
Try it Yourself »
Output
One after another: about 2 s
Together with Promise.all: about 1 s

Start tasks together when they are independent. Only await them one by one when the second task needs the result of the first.

The Four Promise Combinators

MethodWaits forResult
Promise.all(list)All to succeedArray of results. Rejects as soon as one fails
Promise.allSettled(list)All to finishArray of { status, value } or { status, reason }, never rejects
Promise.race(list)The first to finishThat one's result or error
Promise.any(list)The first to succeedIts result. Rejects only if all fail

Promise.all With Destructuring

Promise.all keeps the results in the same order as the list, so destructuring works nicely:

Example

javascript
async function loadDashboard() {
  const [user, orders, offers] = await Promise.all([
    fakeRequest("user", 800),
    fakeRequest("orders", 1200),
    fakeRequest("offers", 500)
  ]);
  console.log(user, "|", orders, "|", offers);
}
loadDashboard();
Output
user loaded | orders loaded | offers loaded

Promise.allSettled: Do Not Fail Everything

If one widget on a page fails, the others should still show. allSettled reports each result separately:

Example

html
<!DOCTYPE html>
<html>
<body>

<ul id="list"></ul>

<script>
function fakeRequest(name, ms, fail = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => fail ? reject(new Error(name + " failed")) : resolve(name + " loaded"), ms);
  });
}

async function loadWidgets() {
  const results = await Promise.allSettled([
    fakeRequest("Weather", 600),
    fakeRequest("Cricket score", 900, true),
    fakeRequest("Petrol price", 400)
  ]);

  for (const r of results) {
    const li = document.createElement("li");
    li.textContent = r.status === "fulfilled" ? r.value : "Error: " + r.reason.message;
    document.getElementById("list").append(li);
  }
}

loadWidgets();
</script>

</body>
</html>
Try it Yourself »
Output
Weather loaded
Error: Cricket score failed
Petrol price loaded

Error Handling Patterns

Wrap await in try...catch. Remember that an async function always returns a promise, so errors inside it become a rejected promise for the caller.

Example

javascript
async function getStudent(id) {
  const response = await fetch("/api/students/" + id);
  if (!response.ok) throw new Error("HTTP " + response.status);
  return response.json();
}

async function showStudent() {
  try {
    const student = await getStudent(7);
    console.log(student.name);
  } catch (error) {
    console.log("Could not load student:", error.message);
  } finally {
    console.log("Hide the loading spinner");
  }
}

A common mistake is forgetting await. Then try...catch cannot catch the error, because the promise fails after the try block has ended.

Cancelling With AbortController

Sometimes you need to stop a request: the user typed a new search, left the page, or the server is too slow. An AbortController gives you a signal to pass to fetch, and an abort() method to cancel it.

Example

javascript
const controller = new AbortController();

fetch("https://jsonplaceholder.typicode.com/posts", { signal: controller.signal })
  .then(res => res.json())
  .then(posts => console.log(posts.length))
  .catch(err => {
    if (err.name === "AbortError") console.log("Request was cancelled");
    else console.log("Other error:", err.message);
  });

controller.abort();   // cancel it straight away
Output
Request was cancelled

Timeouts With AbortSignal.timeout

For a time limit, you do not even need a controller. AbortSignal.timeout(ms) makes a signal that aborts itself:

Example

html
<!DOCTYPE html>
<html>
<body>

<button id="load">Load posts (2 second limit)</button>
<p id="demo"></p>

<script>
document.getElementById("load").addEventListener("click", async () => {
  const out = document.getElementById("demo");
  out.textContent = "Loading...";
  try {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
      signal: AbortSignal.timeout(2000)
    });
    const posts = await res.json();
    out.textContent = "Loaded " + posts.length + " posts.";
  } catch (err) {
    out.textContent = err.name === "TimeoutError"
      ? "The server took too long. Please try again."
      : "Could not load: " + err.message;
  }
});
</script>

</body>
</html>
Try it Yourself »

A timeout signal fails with a TimeoutError, while a manual abort() fails with an AbortError.

Top-level await

In a module (<script type="module">), you can use await directly, outside any function:

Example

javascript
// inside <script type="module">
const res = await fetch("/config.json");
const config = await res.json();

Exercise

Exercise

You load three independent widgets and want to show every one that works, even if one fails. Which method fits best?

Learn JavaScript with a teacher

Join the Web Development course at Workownix, online or in Orangi Town, Karachi, and get a certificate.

See the course Ask on WhatsApp