Lesson Weekend

Let’s start by adding more asynchrony in our applications with setInterval() and setTimeout(). You may have explored these functions before in Introduction to Programming, but even if that’s the case, you’ll be learning a few new tools now. In this lesson, we’ll cover using arrow functions and testing the passage of time with Jasmine.

These functions are perfect (and simple) examples of async code that needs to run later, not now. setTimeout() calls a piece of code once, after a set duration of time. setInterval() calls a piece of code multiple times, with a specific interval of time between each call.

Here are a few real world examples. Let’s say you visit a page and start reading an article. After twenty seconds, a notification pops up asking if you’d like to subscribe to a newsletter. This is a perfect use case for setTimeout(). The code might look something like this:

setTimeout(function(){
  alert("Hello friend! Maybe you should sign up for our newsletter!");
}, 20000);

A few things to note here. The code snippet above takes two arguments. The first is the function that should be called after the specified duration of time. The second is the duration of time that needs to pass before the code is called, in milliseconds. The duration always comes after the function, which can be tricky if the function is particularly long or utilizes callbacks.

There’s one other interesting thing to note about setTimeout(). What if we were to call the function like this:

setTimeout(function(){ 
  //function goes here 
}, 0);

The timeout is set to 0, so is the function asynchronous or not?

Yes, it is. Not only that, but the function might not execute immediately even though the timeout is set to 0. This is because asynchronous code is queued up in JavaScript’s event loop, and when an async function is called, it’s added to the end of the queue. In the past, some developers have intentionally used setTimeout() in this manner to make synchronous code asynchronous. We won’t cover JavaScript’s internals here, but you may want to do further research on your own on how the event loop works.

We’ve covered setTimeout(). What about setInterval()? Let’s say we have an application where we want to refresh and display the current time every second. We’d utilize setInterval() to check and update the time every second instead. This code would look similar to the code snippet above:

setInterval(function(){
  //check and display time every second!
}, 1000);

Once again, the function that needs to be called goes first followed by the duration of the interval between each call.

You can also cancel a setTimeout() or setInterval() with their respective clear methods (clearTimeout() and clearInterval().)

Now that we’ve covered the basics of of using these methods, let’s start building the business logic for a very simple game that takes advantage of these methods. (You’ll get the chance to build an application with more complex logic on Monday.) Perhaps you’ve heard the old saying: “Sometimes you eat the bear, and sometimes the bear eats you.”

In this game, you need to feed the bear regularly or the bear will try to eat you. Specifically, the bear needs to be fed every ten seconds or you’ll get eaten. (It really is very hungry!) This is a perfect opportunity to use setInterval().

We’re going to use test-driven development to set up our logic. That means we’ll start with some tests in Jasmine. Because we are testing the passage of time, we’ll need to use Jasmine Clock to provide helper methods that simulate the passage of time.

Go ahead and set up a project with Jasmine and Karma. Next, create a file in the spec folder called hungrybear-spec.js. Here’s our basic test setup along with a basic test that implements the Jasmine Clock.

spec/hungrybear-spec.js
import { HungryBear } from './../js/hungrybear.js';

describe('HungryBear', function() {
  let fuzzy = new HungryBear("Fuzzy");

  beforeEach(function() {
    jasmine.clock().install();
  });

  afterEach(function() {
    jasmine.clock().uninstall();
  });

  it('should have a name and a food level of 10 when it is created', function() {
    expect(fuzzy.name).toEqual("Fuzzy");
    expect(fuzzy.foodLevel).toEqual(10);
  });


  it('should have a food level of 7 after 3001 milliseconds', function() {
    jasmine.clock().tick(3001);
    expect(fuzzy.foodLevel).toEqual(7);
  });
});

With Jasmine Clock, we need to set up the clock before each test and then tear it down after each test, so we add a beforeEach() and afterEach() block where we install and then uninstall the clock.

Once the clock is installed, we have access to the tick() helper method. Each tick is a millisecond, so when we call jasmine.clock().tick(3001);, that means just over three seconds have passed. When fuzzy is created, he should have a foodLevel of 10. After three seconds, fuzzy’s foodLevel should be down to 7.

Let’s create a js directory in our root directory and then a hungrybear.js file inside of that. It’s time to write some code to make our tests pass:

js/hungrybear.js
export class HungryBear {

  constructor(name) {
    this.name = name;
    this.foodLevel = 10;
  }

  setHunger() {
    setInterval(() => {
      this.foodLevel--;
    }, 1000);
  }
}

You’re already familiar with classes and constructors so let’s focus on the setHunger() function. When setHunger() is called, it needs to decrement the bear’s food level by 1 every second. Within setHunger(), we utilize a callback to setInterval(). Note the use of fat arrow notation here. Remember that this doesn’t have lexical scope inside a nested function unless we use arrow notation, and we don’t want to use the var that = this hack!

If we try running our tests now, the first passes but the second doesn’t. That’s because we don’t call the setHunger() function in our tests. We’ll need to write more tests that utilize setHunger() so let’s add it to our beforeEach() block:

spec/hungrybear-spec.js
beforeEach(function() {
    jasmine.clock().install();
    fuzzy.setHunger();
  });

Now both tests should pass.

Since this is a simple game, we only have a few more small things to implement. We need to be able to feed() the bear and check whether or not we got eaten. Let’s add some more tests:

spec/hungrybear-spec.js
…

  it('should get very hungry if the food level drops below zero', function() {
    fuzzy.foodLevel = 0;
    expect(fuzzy.didYouGetEaten()).toEqual(true);
  });

  it('should get very hungry if 10 seconds pass without feeding', function() {
    jasmine.clock().tick(10001);
    expect(fuzzy.didYouGetEaten()).toEqual(true);
  });

  it('should have a food level of ten if it is fed', function() {
    jasmine.clock().tick(9001);
    fuzzy.feed();
    expect(fuzzy.foodLevel).toEqual(10);
  });
...

The top two tests are variants on each other. The first checks if we get eaten when fuzzy’s food level drops to 0. The second checks if we get eaten after ten seconds pass without a feeding. Both should evaluate to true. Now that we’ve established that fuzzy’s food level goes down, the last test checks if feeding fuzzy after nine seconds brings his foodLevel back up to 10. We also could’ve written this final test by setting fuzzy’s foodLevel to 1 first and then feeding him.

Here are the functions we need to make this pass:

js/hungrybear.js
...
  didYouGetEaten() {
    if (this.foodLevel > 0) {
      return false;
    } else {
      return true;
    }
  }

  feed() {
    this.foodLevel = 10;
  }
...

Now all tests should be passing!

There is still one key piece of functionality missing from this application (other than the UI, which we won’t cover here). A player could still feed the bear after getting ‘eaten' and keep playing. We’ll leave it to you to add this functionality to the application (and implement it in the browser with jQuery if you like).

In this lesson, we’ve gone over using setTimeout() and setInterval() and built a very basic game that uses setInterval(). We also learned how to use Jasmine Clock to test the passage of time and used fat arrow notation to ensure this has context in a nested function. Most importantly, we’ve had the opportunity to practice working with asynchronous JavaScript.