javascriptasync-awaitpromise

What happens to the promise if we don't await for it


I have this function to play audio using a given source and it works fine:

function sfxPlay(source) {
  let sfxAudio = new Audio(`${source}`);
  sfxAudio.crossOrigin = "anonymous";
  sfxAudio.play();

  return new Promise((resolve) => {
    sfxAudio.addEventListener("ended", function () {
      sfxAudio.currentTime = 0;
      resolve();
    });
  });
}

I will use it inside an async function like this:

await sfxPlay(source.mp3);

The question is what if I want to use the function without await? Does this make any errors if later I use it with await again... what happens to that promise when we don't await for it?!

EDIT: I mean if we have the function like this inside function A:

sfxPlay(source.mp3);

Is it acceptable to use it inside function B like this?

await sfxPlay(source.mp3);

Solution

  • In most cases, calling the function is what starts the promise, and you only need to await it if you care about the results (or to do error handling).

    We can test this ourselves with a simple little promise definition:

    let x = 0; // avoid global state, this is just for demonstration
    
    function test() {
      return new Promise((resolve) => {
        setTimeout(() => {
          x++;
          resolve();
        }, 1000);
      });
    }
    

    If you await this promise, it will resolve after 1 second, and then you'll immediately be able to use the updated value of x in the code below:

    await test();
    console.log(x); // will be 1, because we wait for the promise to resolve
    

    If you just call the promise without a .then or an await though, it will still run, you just won't know when it finishes:

    test();
    console.log(x); // will be 0, because the promise is still running
    
    // more than one second later ...
    
    console.log(x); // will be 1, because the promise has resolved in the meantime
    

    Sometimes, this is definitely what you want. In OP's example their function to play a sound effect returns a promise, that is definitely the sort of thing where if you just don't care about the result and you know it doesn't throw, then calling it like this might just be the way to go. Personally, I like putting the void operator in front, just to make it obvious that this behaviour is intended, but this has no functional effect:

    void test();
    

    Errors / rejections

    For promises that aren't awaited / don't have a .catch on them, any rejections will be thrown at the global level rather than scoped to the location where you called the promise. By default, these exceptions will be completely unhandled, so it's probably a good idea to at least always have a .catch if you know this is a possibility.

    "Promise like" objects

    Technically speaking, anything that roughly matches this interface is "promise like" and can be awaited:

    interface PromiseLike<T> {
      then(resolve: (value: T) => void, reject: () => void): void;
    }
    

    A lot of e.g. database ORMs use this hack to create their own "promises" that only run when they are actually awaited (because await just calls the then function, which in their implementation is what actually starts running the query), but you can run across similar objects in a number of other places too. This can be beneficial, e.g. ORMs often do this so that you can pass queries around while building them, and then await the query to run it once you're happy with it.

    In most of those cases, you must actually await the "promise like" object (or call .then on it), since they don't necessarily behave like a true Promise object would.