JavaScript
Async Programming
Sound Playback
Web Development
Audio API

Async play sound in javascript?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Playing sound in JavaScript is already asynchronous in the practical sense that audio playback does not block the rest of the page. What developers usually need help with is waiting for loading, handling the play() promise correctly, and respecting browser autoplay restrictions.

The Simple Case: HTMLAudioElement

For many apps, the easiest option is the built-in Audio element.

html
1<button id="play">Play sound</button>
2<script>
3  const audio = new Audio("/sounds/click.mp3");
4
5  document.getElementById("play").addEventListener("click", async () => {
6    try {
7      await audio.play();
8      console.log("playing");
9    } catch (error) {
10      console.error("play failed", error);
11    }
12  });
13</script>

audio.play() returns a promise in modern browsers. That matters because playback can fail if the browser blocks autoplay or if the media is not ready.

Why await audio.play() Is Useful

You do not use await because the sound blocks JavaScript. You use it so your code can react to success or failure in a structured way.

For example, you may want to:

  • show an error if playback is blocked
  • start another effect only after playback begins
  • avoid overlapping sounds accidentally

So the async part is mostly about control flow, not about making the browser "multithreaded."

Loading Audio Data Explicitly

If you want more control, use fetch plus the Web Audio API.

html
1<button id="play-buffer">Play buffer</button>
2<script>
3  const audioContext = new AudioContext();
4  let audioBuffer;
5
6  async function loadBuffer() {
7    const response = await fetch("/sounds/click.mp3");
8    const arrayBuffer = await response.arrayBuffer();
9    audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
10  }
11
12  document.getElementById("play-buffer").addEventListener("click", async () => {
13    if (!audioBuffer) {
14      await loadBuffer();
15    }
16
17    const source = audioContext.createBufferSource();
18    source.buffer = audioBuffer;
19    source.connect(audioContext.destination);
20    source.start();
21  });
22</script>

This pattern is useful when you need low-latency repeated playback, mixing, filtering, or precise timing.

Autoplay Restrictions Are Often The Real Issue

A lot of "async audio" questions are actually autoplay-policy questions. Browsers often block playback until the user interacts with the page.

That is why examples typically attach playback to a click or touch event. If you call play() on page load, the promise may reject.

So if sound does not start, check whether the problem is:

  • no user gesture yet
  • the file failed to load
  • the promise from play() rejected
  • the audio context is suspended and must be resumed

Replaying The Same Sound

If you reuse the same Audio element, repeated rapid playback may restart the sound rather than overlap it. For UI sound effects, developers often clone the element or use the Web Audio API.

javascript
1function playClick() {
2  const sound = new Audio("/sounds/click.mp3");
3  sound.play().catch(console.error);
4}

This is simple, though not the most efficient approach for high-frequency effects. Preloaded buffers are usually better for games or audio-heavy interfaces.

Handle Failures Explicitly

Because playback is promise-based, do not ignore errors.

javascript
audio.play().catch((error) => {
  console.error("Could not play audio:", error);
});

That makes debugging much easier than assuming every play() call succeeds.

Common Pitfalls

The most common mistake is thinking you need asynchronous code to keep JavaScript from blocking during playback. Playback itself is already non-blocking.

Another mistake is ignoring the promise returned by play(), which hides autoplay-policy failures.

Developers also often try to start sound before any user interaction, then misdiagnose the browser's autoplay block as a loading problem.

Finally, for short repeated sounds, a single Audio element may not behave the way you want. Use the Web Audio API or separate instances when overlap matters.

Summary

  • Sound playback does not block the page, but loading and control flow are asynchronous.
  • 'audio.play() returns a promise and should be handled explicitly.'
  • Use HTMLAudioElement for simple playback and Web Audio API for more control.
  • Expect browser autoplay restrictions until the user interacts with the page.
  • Choose your playback strategy based on whether sounds need to overlap, preload, or be timed precisely.

Course illustration
Course illustration

All Rights Reserved.