DEV Community

Ryan Ameri
Ryan Ameri

Posted on

Mastering Hard Parts of JavaScript: Closure II

Exercise 6

Write function delay that accepts a callback as the first parameter and the wait in milliseconds before allowing the callback to be invoked as the second parameter. Any additional arguments after wait are provided to func when it is invoked. HINT: research setTimeout();

Solution 6

function delay(func, wait, ...rest) {
  function delayRun() {
    func(...rest);
  }
  setTimeout(delayRun, wait);
}

A couple of things should be noted here. First of all, using rest parameters to make sure that all the following parameters are passed on to the inner function.

Secondly, notice that the function return technically doesn't return anything. It simply uses the setTimeout() which is a API provides by the browser/node.js. It is setTimeout that invokes the delayRun function, with a delay of wait milliseconds. And yet thanks to closure, inside delayRun we still have access to all the parameters that were passed to delay.

Exercise 7

Write a function rollCall that accepts an array of names and returns a function. The first time the returned function is invoked, it should log the first name to the console. The second time it is invoked, it should log the second name to the console, and so on, until all names have been called. Once all names have been called, it should log 'Everyone accounted for'.

function rollCall() {}

const rollCaller = rollCall(["Victoria", "Juan", "Ruth"]);
rollCaller(); // => should log 'Victoria'
rollCaller(); // => should log 'Juan'
rollCaller(); // => should log 'Ruth'
rollCaller(); // => should log 'Everyone accounted for'

Solution 7

function rollCall(names) {
  let counter = 0;
  function runCall() {
    if (counter < names.length) {
      console.log(names[counter]);
      counter++;
    } else {
      console.log("Everyone accounted for");
    }
  }
  return runCall;
}

This is similar to exercise 5, in that we need to output different things based on how many times the function has been called. So immediately you should think, we need a counter, and this counter needs to be in the outside scope. After that it's pretty simple, our function receives an array and we just need to console.log a different element of that array based on how many times our function has been called. Simple, yet so beautiful!

Exercise 8

Create a function saveOutput that accepts a function (that will accept one argument), and a string (that will act as a password). saveOutput will then return a function that behaves exactly like the passed-in function, except for when the password string is passed in as an argument. When this happens, the returned function will return an object with all previously passed-in arguments as keys, and the corresponding outputs as values.

function saveOutput() {}
const multiplyBy2 = function (num) {
  return num * 2;
};
const multBy2AndLog = saveOutput(multiplyBy2, "boo");
console.log(multBy2AndLog(2));
// => should log 4
console.log(multBy2AndLog(9));
// => should log 18
console.log(multBy2AndLog("boo"));
// => should log { 2: 4, 9: 18 }

Solution 8

function saveOutput(func, magicWord) {
  const log = {};
  function funcAndLog(num) {
    if (num !== magicWord) {
      log[num] = func(num);
      return log[num];
    } else {
      return log;
    }
  }
  return funcAndLog;
}

Now we are expanding our function's memory to more than just a counter. Instead of simply counting how many times the function has been called, we have to keep track of all the parameters that our function receives and the output values that our function returns.

So we need an empty object, and this object needs to reside in the outside scope so that it's persistent. Beyond that, it's rather simple. In our closure function we check if the magic password has been given. If it not, we record the parameter and its value and return that value. If the magic password has been given, we return our whole log function which contains all the parameters and the returned values previously stored.

Exercise 9

Create a function cycleIterator that accepts an array, and returns a function. The returned function will accept zero arguments. When first invoked, the returned function will return the first element of the array. When invoked a second time, the returned function will return the second element of the array, and so forth. After returning the last element of the array, the next invocation will return the first element of the array again, and continue on with the second after that, and so forth.

function cycleIterator() {}
const threeDayWeekend = ["Fri", "Sat", "Sun"];
const getDay = cycleIterator(threeDayWeekend);
console.log(getDay()); // => should log 'Fri'
console.log(getDay()); // => should log 'Sat'
console.log(getDay()); // => should log 'Sun'
console.log(getDay()); // => should log 'Fri'

Solution 9

function cycleIterator(array) {
  let counter = 0;
  function cyclingItems() {
    counter++;
    return array[(counter - 1) % array.length];
  }
  return cyclingItems;
}

This is similar to exercise 7, in that we have to keep count of how many times the function has been called and return an item from the original parameter array accordingly. The only difference here is that when we run out of the array items, we need to go back to the beginning of the array. So basically we need to use the mod operator to continuously cycle through the array.

Exercise 10

Create a function defineFirstArg that accepts a function and an argument. Also, the function being passed in will accept at least one argument. defineFirstArg will return a new function that invokes the passed-in function with the passed-in argument as the passed-in function's first argument. Additional arguments needed by the passed-in function will need to be passed into the returned function.

function defineFirstArg() {}
const subtract = function (big, small) {
  return big - small;
};
const subFrom20 = defineFirstArg(subtract, 20);
console.log(subFrom20(5)); // => should log 15

Solution 10

function defineFirstArg(func, arg) {
  function insideFn(second) {
    return func(arg, second);
  }
  return insideFn;
}

Reading the description of the exercise made my head spin a little bit! But thankfully looking at the expected output cleared it up a bit. Basically our function needs to return an inside function, and this function needs to run a function that was originally given as a parameter to the outside function.

I believe this is basically a very gentle introduction to the concept of currying.

Latest comments (3)

Collapse
 
kris_y_3eb8bb541850608671 profile image
Kris Y

Can i write challenge 6 in this way:
function delay(fn, wait, ...rest) {
function delayRun () {
setTimeout(() => {
fn(...rest)
}, wait);
};
return delayRun;
}

Collapse
 
mrjoshi profile image
Kamal joshi

in solution 8 for more optimization u can do
function saveOutput(func, magicWord) {
const log = {};
return function(x){
if(x==magicWord)
return log;
else if(log[x]){
console.log("from cache")
return log[x];}
else
{
log[x]=func(x);
return log[x];
}

}
}

/*** Uncomment these to check your work! ***/
const multiplyBy2 = function(num) { return num * 2; };
const multBy2AndLog = saveOutput(multiplyBy2, 'boo');
console.log(multBy2AndLog(2)); // => should log 4
console.log(multBy2AndLog(9)); // => should log 18
console.log(multBy2AndLog('boo')); // => should log { 2: 4, 9: 18 }
console.log(multBy2AndLog(9)); // => should log 18

Collapse
 
7qruzer profile image
Kumar Gaurav

In Solution 6, paragraph 2, first line,

Secondly, notice that the function "delay" (and not "return") technically doesn't return anything.