DEV Community

Ryan Ameri
Ryan Ameri

Posted on • Edited on

Mastering Hard Parts of JavaScript: Asynchronicity III

Exercise 6

Write a function called everyXsecsForYsecs that will accept three arguments: a function func, a number interval, and another number duration. everyXsecsForYsecs will execute the given function every interval number of milliseconds, but then automatically stop after duration milliseconds. Then pass the below sayHi function into an invocation of everyXsecsForYsecs with 1000 interval time an 5000 duration time. What do you expect to happen?

function everyXsecsForYsecs() {}
function theEnd() {
  console.log("This is the end!");
}
everyXsecsForYsecs(theEnd, 2, 20);
// should invoke theEnd function every 2 seconds, for 20 seconds): This is the end!

Solution 6

function everyXsecsForYsecs(func, interval, duration) {
  const id = setInterval(func, interval * 1000);
  function clear() {
    clearInterval(id);
  }
  setTimeout(clear, duration * 1000);
}

This turns out to be very similar to the previous exercise, another way of practicing setInterval and clearInterval. Here the function to be executed is passed on as an argument, but other than that everything should look familiar.

Exercise 7

Write a function delayCounter that accepts a number (called 'target') as the first argument and a number of milliseconds (called 'wait') as the second argument, and returns a function.

When the returned function is invoked, it should log to the console all of the numbers between 1 and the target number, spaced apart by 'wait' milliseconds.

function delayCounter() {}

const countLogger = delayCounter(3, 1000);
countLogger();
//After 1 second, log 1
//After 2 seconds, log 2
//After 3 seconds, log 3

Solution 7

function delayCounter(target, wait) {
  function closureFn() {
    let i = 1;
    const id = setInterval(() => {
      console.log(i);
      i++;
      if (i > target) clearInterval(id);
    }, wait);
  }
  return closureFn;
}

We're putting all the concepts we've practiced on callbacks, closure and asynchronicity to good use here! The description demands that our function should return another function, so we're talking closure. We're also calling clearInterval in the callback function given to setInterval. Every time setInterval is invoked, we increment our counter i that's declared in the outside scope (our memory). We check to make sure that our counter is still lower than our target, and when it goes above that, we execute clearInterval.

Exercise 8

Write a function, promised, that takes in a value. This function will return a promise that will resolve after 2 seconds.

function promised() {}

const createPromise = promised("wait for it...");
createPromise.then((val) => console.log(val));
// will log "wait for it..." to the console after 2 seconds

Solution 8

function promised(val) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(val), 2000);
  });
}

If you are not familiar with the syntax of a Promise (hint: there's always MDN) this can look a bit confusing. The important thing to remember is that a promise can take one or two parameters, the first is the function to be called when the promise is resolved, and the second (optional, not shown here) is the function to be called when the operation fails.

So in this exercise, we are creating a Promise and returning it. The resolve function is given to the Promise when the .then method is called on it. Here we just execute that function with a setTimeout set to 2 seconds.

Exercise 9

Write a SecondClock class, with two methods: start and reset.​
start: upon invocation, invokes a callback (this.cb, defined in the constructor) on an argument every second, with the argument to the callback being the current seconds "value".

In other words, the callback gets invoked every second on the "seconds hand" of the clock. Always start with 1 and don't utilize the seconds value of the current computer clock time.

The first "tick" with value 1 occurs 1 second after the initial "secondClock" invocation.
The second "tick" with value 2 occurs 2 seconds after the initial "secondClock" invocation.

The sixtieth "tick" with value 60 occurs 60 seconds after the initial "secondClock" invocation.
The sixty-first "tick" with value 1 occurs 61 seconds after the initial "secondClock" invocation. The sixty-second "tick" with value 2 occurs 62 seconds after the initial "secondClock" invocation.
etc.

reset: upon invocation, completely stops the "clock". Also resets the time back to the beginning Hint: look up setInterval and clearInterval.

class SecondClock {}

const clock = new SecondClock((val) => {
  console.log(val);
});
console.log("Started Clock.");
clock.start();
setTimeout(() => {
  clock.reset();
  console.log("Stopped Clock after 6 seconds.");
}, 6000);

Solution 9

class SecondClock {
  constructor(cb) {
    this.cb = cb;
    this.seconds = 0;
    this.id = undefined;
  }
  start() {
    this.id = setInterval(() => {
      this.seconds++;
      this.cb(this.seconds % 60);
    }, 1000);
  }
  reset() {
    this.seconds = 0;
    clearInterval(this.id);
  }
}

The description again looks a bit daunting, but as always the challenge in solving the problem requires breaking it down into simpler parts. Solving this exercise requires a bit of a knowledge of the class syntax as well, which we will practice a lot in the next section of this series.

What this exercise is trying to show us is how to implement something very similar to exercise 7, but here using class structure instead of closure. So instead of having an outer variable that acts as our memory, here our memory is a class field. We've got two class methods, start and reset that basically manipulate our counter using a callback function that's first given to us in the constructor.

Exercise 10

Write a function called debounce that accepts a function and returns a new function that only allows invocation of the given function after interval milliseconds have passed since the last time the returned function ran.

Additional calls to the returned function within the interval time should not be invoked or queued, but the timer should still get reset.

For examples of debouncing, check out this CSS Tricks article.

function debounce() {}

function giveHi() {
  return "hi";
}
const giveHiSometimes = debounce(giveHi, 3000);
console.log(giveHiSometimes());
// should output 'hi'
setTimeout(function () {
  console.log(giveHiSometimes());
}, 2000);
// should output undefined
setTimeout(function () {
  console.log(giveHiSometimes());
}, 4000);
//should output undefined
setTimeout(function () {
  console.log(giveHiSometimes());
}, 8000);
// should output 'hi'

Solution 10

function debounce(callback, interval) {
  let counter = 0;
  let hasRan = false;
  function closureFn() {
    let id = undefined;
    if (!hasRan) {
      ///this is the first run
      id = setInterval(() => counter++, 1);
      hasRan = true;
      return callback();
    } else {
      //for subsequent runs
      if (counter < interval) {
        // Not enough time has elapsed
        counter = 0;
        clearInterval(id);
        id = setInterval(() => counter++, 1);
        return undefined;
      } else {
        //Enough time has elapsed
        counter = 0;
        clearInterval(id);
        id = setInterval(() => counter++, 1);
        return callback();
      }
    }
  }
  return closureFn;
}

Debouncing and throttling are important concepts in modern web development (this functionality is provided by many libraries). Here we're implementing a simple debounce using closure and callbacks. We need a counter and a flag to indicate whether the function has ran before before, these variables need to reside in our memory, so in the outer scope. We then increment the counter using setInterval, and in subsequent runs we check to see if enough time has passed or not (based on interval). If enough time has not passed, we need to reset the counter and return undefined. If enough time has passed, we again reset the counter but this time execute and return the callback.

This brings our asynchronicity chapter to a close. Next we'll take a closer look at class and the prototype.

Top comments (3)

Collapse
 
philosphi profile image
Phi Nguyen • Edited

For ex 10 I declared a runTImer function outside the returned function. Whenever the debounced function is called, timeElapsed is set to 0. I do not call clearInterval since the timer can just continue on anyways.

function debounce(callback, interval) {
  // ADD CODE HERE
  let timeElapsed = 0;
  const runTimer = () => setInterval(() => timeElapsed += 100, 100)
  function func() {
    console.log(timeElapsed)
    if (timeElapsed === 0) {
      runTimer()
      return callback()
    }
    else if (timeElapsed < interval) {
      timeElapsed = 0;
    }
    else {
      timeElapsed = 0;
      return callback()
    }
  }
  return func
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
philosphi profile image
Phi Nguyen

For ex 7 we can also use the previous function we defined in ex 6!

function delayCounter(target, wait) {
  let i = 1;
  const func = () => {
    everyXsecsForYsecs(() => console.log(i++), 1, target)
  }
  return func;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
danbaba1 profile image
Danbaba1

cool