この解答例はChatGPTによって生成されています,正しいかは自己判断で。
BFE.devでのAI解答例 - JavaScript コーディング問題
84. fake timer (setInterval)を実装する
class FakeTimer {
install() {
const originSetInterval = window.setInterval;
const originClearInterval = window.clearInterval;
const originDateNow = Date.now;
let currentTime = 0;
let intervalIdMap = new Map();
window.setInterval = function (fn, delay) {
let id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
const intervalFunc = () => {
fn();
intervalIdMap.set(id, originSetInterval(intervalFunc, delay));
};
intervalIdMap.set(id, originSetInterval(intervalFunc, delay));
return id;
};
window.clearInterval = function (intervalId) {
const interval = intervalIdMap.get(intervalId);
if (interval) {
originClearInterval(interval);
intervalIdMap.delete(intervalId);
}
};
Date.now = function () {
return currentTime;
};
let previousTickTime = Date.now();
this.tick = function (duration = 0) {
const ticksCount = Math.ceil(duration / 10);
const newCurrentTime = currentTime + duration;
for (let i = 0; i < ticksCount; i++) {
previousTickTime = currentTime;
currentTime += 10;
intervalIdMap.forEach((interval, id) => {
if (currentTime - previousTickTime === interval.delay) {
interval.func();
}
});
}
};
}
uninstall() {
window.setInterval = originSetInterval;
window.clearInterval = originClearInterval;
Date.now = originDateNow;
}
tick() {
this.tick(10);
}
}
補足
setInterval
関数をトラッキングするためにintervalIdMap
という Map を定義して、setInterval
で生成した id をキー、interval id と interval function のオブジェクトを値として保存するようにしています。setInterval
をそれぞれのdelay
毎に実行し、intervalIdMap
を使ってclearInterval
を呼び出すには、以下のようにします。- id に対応する interval object を取得
- 値を実行して、次の実行をスケジューリング
currentTime
の変数は常に時間を格納し、Date.now()
への置き換え関数のために使用されています。tick()
関数は指定されたduration
の時間だけイベントループを進めています。tick()
メソッド内のループでは、intervalIdMap
内のすべてのinterval object
をチェックして、currentTime - previousTickTime
タイムアウトした場合に、オブジェクトの機能を実行しています。random
で生成されたid
がシステム上で一意であることを保証するために、setInterval
によって生成される整数の範囲を最大整数に制限しました。
let id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
テストの例
const fakeTimer = new FakeTimer();
fakeTimer.install();
const logs = [];
const log = () => {
logs.push(Date.now());
};
let count = 0;
const id = setInterval(() => {
if (count > 1) {
clearInterval(id);
} else {
log();
}
count += 1;
}, 100);
// log 'A' at every 100, stop at 200
fakeTimer.tick(200);
fakeTimer.uninstall();
expect(logs).toEqual([100, 200]);
// Test for setInterval and clearInterval
const intervals = [];
const intervalsMap = new Map();
const callback = () => {
console.log('Callback');
};
const createInterval = (cb, delay) => {
const id = setInterval(cb, delay);
intervals.push(id);
intervalsMap.set(id, { cb, delay });
};
createInterval(callback, 100);
createInterval(callback, 1200);
fakeTimer.tick(2600);
intervals.forEach((intervalId) => clearInterval(intervalId));
const intervalsStats = [
{
intervalFn: () => console.log('Callback 1'),
delay: 100,
calls: 26
},
{
intervalFn: () => console.log('Callback 2'),
delay: 1200,
calls: 2
}
];
let callStats = intervalsStats.map((v) => ({ ...v, calls: 0 }));
for (let i = 0; i < callStats.length; i++) {
let intervalInvocations = intervalsMap.get(intervals[i]);
let intervalFn = intervalInvocations['cb'];
let delay = intervalInvocations['delay'];
for (let j = 0; j < callStats[i]['calls']; j++) {
expect(logs.shift()).toBe(j * delay + (i === 0 ? 100 : 0));
}
}