• News
  • Games
  • About
  • Cover image

    Observables fix broken Promises

    Promises may have been made to be broken, but with RxJS Observables you'll experience more manageable asynchronous operations. By making this shift, you not only simplify your code but also address a critical drawback of Promises: their inability to be canceled.

    Countdown

    Three, two, one, GO!

    blog post image

    I'm working on my game, and I'm adding a countdown to start the game automatically if we don't receive any user input for a few seconds. Sounds easy enough, but you'll end up pulling your hair out unless you code it carefully. In JavaScript, we can use setTimeout() or setInterval() to handle this, but dismissing them correctly can be more difficult than it sounds.

    I decided to write this blog because I got frustrated with why my code didn't work as expected. My countdown was at times being displayed too early, and sometimes two different counters would be displayed at the same time, and it was all a mess. I had code running asynchronously, and not stopping properly, or running when it shouldn't.

    If you're working with Phaser, the best 2D game engine for making games in JavaScript (or TypeScript,) you can also combine the update loop with an internal counter, which I think is a better option than setTimeout() or setInterval(). The downside is that we clutter our Scene with code that doesn't need to be there. Wouldn't it be better to create a Promise that resolves after a few seconds, and then start the countdown? Sounds like a good idea, but here's the catch: we need to cancel the countdown after receiving input from the user. Promises do not have that functionality built-in, so you need to work around it.

    Let's take a look at the following example:

    countdown1.js

    import { waitForSeconds } from 'utils';
    
    function handleCountdown() {
      waitForSeconds(5).then(() => {
        startCountdown();
      })
    }

    We can call this function when we start our game. Let's ignore the implementation of waitForSeconds for the moment. It returns a `Promise` that will resolve after a given number of seconds (5 in the above example.) What do we do if we want to cancel the countdown before it even starts?

    One option is to create a condition inside the function to avoid starting the countdown, which is a valid option, but it can get cluttered, and we have to maintain the state correctly.

    countdown2.js

    import { waitForSeconds } from 'utils';
    
    let didUserNavigate = false;
    
    function handleCountdown() {
      waitForSeconds(5).then(() => {
        if(didUserNavigate) return; // problem solved, kind of
        startCountdown();
      })
    }
    
    function navigateToNextScene() {
      didUserNavigate = true;
    }