I was in a job interview pair programming session where I was asked to implement
JavaScript’s setInterval
method without using setInterval
itself. I did
poorly at that interview for many reasons, including not knowing how to
implement this.
The question
Implement the setInterval function in such a way that executing
new SetInterval.prototype.start
will start the function passed to it and run the function at intervalsinterval
until theSetInterval.prototype.clear
method is called.
For example:
const theIntervalRunner = new SetInterval(() => console.log("logging"), 1000);
theIntervalRunner.start();
setTimeout(() => theIntervalRunner.clear(), 5000);
will log logging 5 times on the console/terminal at an interval of 1000ms.
Use this as a template
function SetInterval(fn, interval) {}
SetInterval.prototype.start = function () {};
SetInterval.prototype.clear = function () {};
I had not encountered a situation where I needed to implement something like
this prior to the interview. I was also too tensed to see that I could have
interpreted the SetInterval
function as a JavaScript class constructor in my
mind, and then implementing it would have been easier. Notwithstanding, this
question opened me up to a lot of my flaws in understanding JavaScript.
My solution (after the interview)
After reading Michael Zheng’s implementation at Implement setInterval with setTimeout, I modified his implementation to fit the interview question.
function SetInterval(fn, interval) {
this.fn = fn.bind(this);
this.interval = interval;
this.toContinue = true;
}
SetInterval.prototype.start = function () {
const wrapper = () => {
if (this.toContinue) {
this.fn();
// returning the id in case it may be needed.
// Not really necessary for this particular implementation
return setTimeout(wrapper, this.interval);
}
return;
};
wrapper();
};
SetInterval.prototype.clear = function () {
this.toContinue = false;
};
const theFn = new SetInterval(function () {
console.log("logging");
}, 1000);
theFn.start();
setTimeout(() => {
theFn.clear();
}, 5000);
You can copy this and run it in your browser console to test it 😌
Explanation
Interpret the SetInterval
function as a JavaScript class for easier
understanding. See it as
class SetInterval {
constructor(fn, interval) {
this.fn = fn.bind(this);
this.interval = interval;
this.toContinue = true;
}
}
When a new SetInterval
object is created via new SetInterval(fn, interval)
,
the function fn
that will run, and the interval are both passed in. The value
of this
in the function argument fn
passed in, is replaced with the object
instance that will be created by the SetInterval
constructor function using
bind
. This is done so that any reference to this
in fn
will refer to the
object instance created by SetInterval
. The result of executing bind
is
passed to this.fn
, which is fn
but with its this
referring to the object
created by SetInterval
.
The object’s toContinue
property is set to track when the running of this.fn
at intervals should stop.
start () {
const wrapper = () => {
if(this.toContinue){
this.fn()
// returning the id in case it may be needed.
// Not really necessary for this particular implementation
return setTimeout(wrapper, this.interval);
}
return;
};
wrapper()
}
start
is a method on object instances created by SetInterval
. When start
is executed, a reference to wrapper
is first stored in start
’s local memory,
and then wrapper
is executed.
On executing wrapper
, the object’s toContinue
property is checked for a
truthy value. For a truthy value, this.fn
is executed wrapper
is set to be
executed on the next this.interval
milliseconds using
setTimeout(wrapper, this.interval)
. This line in particular stores the
setTimeout
function in JavaScript’s memory for events, and schedules wrapper
to run in the next this.interval
milliseconds.
How do we clear/stop the interval function executions?
clear () {
this.toContinue = false
}
clear
is also a method on object instances created by invoking SetInterval
as a constructor. Executing this.clear
sets the object’s toContinue
property
to a falsy value. When executed, the next execution of wrapper
through the
setTimeout
in start
finds out that this.toContinue
is currently falsy, and
then doesn’t execute this.fn
or store another wrapper
function to be
executed in another this.interval
milliseconds.
That’s why the code snippet below
const theFn = new SetInterval(function () {
console.log("logging");
}, 1000);
theFn.start();
setTimeout(() => {
theFn.clear();
}, 5000);
will log logging five times.