Prevent recursive function running setTimeout from compounding - javascript

I wrote a Chip-8 emmulator in JavaScript (source) and made a playable browser version here.
It contains an HTML select:
<select>
<option value="PONG">PONG</option>
<option value="TETRIS">TETRIS</option>
</select>
Which loads a ROM from a file every time one is selected:
document.querySelector('select').addEventListener('change', event => {
const rom = event.target.value
loadRom(rom)
})
This loadRom function fetches the ROM, converts it to a useful form, and loads it into an instance of a CPU class. The cpu has a fetch-decode-execute cycle that gets called with step(). I created this cycle (main) function to call itself in a setTimeout.
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
cpu.interface.clearDisplay()
cpu.load(romBuffer)
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) {
cpu.tick()
timer = 0
}
await cpu.step()
setTimeout(cycle, 3)
}
cycle()
}
This works fine, until I load a new ROM with the select. Now the cycle is compounded, and the game goes twice as fast. Every time you load a new ROM, it compounds again and creates a new cycle.
How can I create an infinite loop, but stop it and start a brand new one without compounding it?

To start with, have the current timeout to be a persistent variable, and then call clearTimeout with it right before calling loadRom. If nothing has been loaded yet, the clearTimeout just won't do anything.
But because you have awaits as well, you'll need to check whether a new rom gets loaded while the awaits are going on. One way to accomplish this would be to have another persistent variable, the current romBuffer being used - if it's not the same as the romBuffer in the function closure, then another rom has started, so return immediately (and don't recursively create a timeout).
let timeout;
let currentRomBuffer;
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
currentRomBuffer = romBuffer;
cpu.interface.clearDisplay()
cpu.load(romBuffer)
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) {
cpu.tick()
timer = 0
}
await cpu.step();
if (romBuffer !== currentRomBuffer) {
return;
}
timeout = setTimeout(cycle, 3);
}
cycle()
};

Try using the setInerval and keep track of the handle.
Then clear it when loading the new (or the same) rom.
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
cpu.interface.clearDisplay()
cpu.load(romBuffer)
// if rom is loaded, clear it!
if(this.lastLoad)
clearInterval(this.lastLoad);
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) cpu.tick()
await cpu.step()
}
// keep track the handle.
this.lastLoad = setInterval(cycle, 3);
}

Related

Measuring await time in Node

Let's say I have a function in Node
const my_function = async () => {
await sub_function_1();
await sub_function_2();
await sub_function_3();
}
I could profile how long it takes my_function to run with:
let t0 = new Date()
await my_function()
let t1 = new Date()
let elapsed_ms = t1 - t0
But that wouldn't show me how long each of the sub_functions took to run. Similarly, I would add profiling code around each of the sub_functions, but that wouldn't show me which one of the sub-sub functions took the longest amount of time to run.
Is there a way, for a given function call, the gather all of the awaited functions underneath that call, along with how long each took to resolve?
what about a wrapper function which take a array of functions and runs them sequentially?
//simulating a lengthy request
const sub_function = (time) => {return new Promise((res,rej) => setTimeout( () => res(true),time ) )}
const new_list = [
{handler:sub_function(90), alias:"sub_function_1"},
{handler:sub_function(150), alias:"sub_function_2"},
{handler:sub_function(350), alias:"sub_function_3"},
]
const calculate_max_time = async list =>{
const time_passed = []
for (const func of list) {
const time = Date.now()
await func.handler
const time2 = Date.now()
time_passed.push({alias:func.alias, value:time2 - time})
}
const max_time = time_passed.reduce( (max,func) => max.value > func.value ? max : func)
console.log(max_time)
}
calculate_max_time(new_list)

Sleep / delay inside promise.all

I am building a backend to handle pulling data from a third party API.
There are three large steps to this, which are:
Delete the existing db data (before any new data is inserted)
Get a new dataset from the API
Insert that data.
Each of these three steps must happen for a variety of datasets - i.e. clients, appointments, products etc.
To handle this, I have three Promise.all functions, and each of these are being passed individual async functions for handling the deleting, getting, and finally inserting of the data. I have this code working just for clients so far.
What I'm now trying to do is limit the API calls, as the API I am pulling data from can only accept up to 200 calls per minute. To quickly test the rate limiting functionality in code I have set it to a max of 5 api calls per 10 seconds, so I can see if it's working properly.
This is the code I have so far - note I have replaced the name of the system in the code with 'System'. I have not included all code as there's a lot of data that is being iterated through further down.
let patientsCombinedData = [];
let overallAPICallCount = 0;
let maxAPICallsPerMinute = 5;
let startTime, endTime, timeDiff, secondsElapsed;
const queryString = `UPDATE System SET ${migration_status_column} = 'In Progress' WHERE uid = '${uid}'`;
migrationDB.query(queryString, (err, res) => {
async function deleteSystemData() {
async function deleteSystemPatients() {
return (result = await migrationDB.query("DELETE FROM System_patients WHERE id_System_account = ($1) AND migration_type = ($2)", [
System_account_id,
migrationType,
]));
}
await Promise.all([deleteSystemPatients()]).then(() => {
startTime = new Date(); // Initialise timer before kicking off API calls
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function getSystemAPIData() {
async function getSystemPatients() {
endTime = new Date();
timeDiff = endTime - startTime;
timeDiff /= 1000;
secondsElapsed = Math.round(timeDiff);
if (secondsElapsed < 10) {
if (overallAPICallCount > maxAPICallsPerMinute) {
// Here I want to sleep for one second, then check again as the timer may have passed 10 seconds
getSystemPatients();
} else {
// Proceed with calls
dataInstance = await axios.get(`${patientsPage}`, {
headers: {
Authorization: completeBase64String,
Accept: "application/json",
"User-Agent": "TEST_API (email#email.com)",
},
});
dataInstance.data.patients.forEach((data) => {
patientsCombinedData.push(data);
});
overallAPICallCount++;
console.log(`Count is: ${overallAPICallCount}. Seconds are: ${secondsElapsed}. URL is: ${dataInstance.data.links.self}`);
if (dataInstance.data.links.next) {
patientsPage = dataInstance.data.links.next;
await getSystemPatients();
} else {
console.log("Finished Getting Clients.");
return;
}
}
} else {
console.log(`Timer reset! Now proceed with API calls`);
startTime = new Date();
overallAPICallCount = 0;
getSystemPatients();
}
}
await Promise.all([getSystemPatients()]).then((response) => {
async function insertSystemData() {
async function insertClinkoPatients() {
const SystemPatients = patientsCombinedData;
Just under where it says ' if (secondsElapsed < 10) ' is where I want to check the code every second to see if the timer has passed 10 seconds, in which case the timer and the count will be reset, so I can then start counting again over the next 10 seconds. Currently the recursive function is running so often that an error displayed related to the call stack.
I have tried to add a variety of async timer functions here but every time the function is returned it causes the parent promise to finish executing.
Hope that makes sense
I ended up using the Bottleneck library, which made it very easy to implement rate limiting.
const Bottleneck = require("bottleneck/es5");
const limiter = new Bottleneck({
minTime: 350
});
await limiter.schedule(() => getSystemPatients());

Wait until an external operation completes with setInterval before finishing

I got a JS code that simply activates a method. This method invokes an HTTP request which is causing some remote process to begin. I then need to check every 5 seconds if the remote process ended with a timeout of 5 minutes, after which I need to stop the waiting and throw an error if the timeout had expired, otherwise I need to simply log the result and complete the method.
What I'm not sure is how do I stop the execution of the main method until I get the response so I can have a value to log. This is what I got so far:
(async function(param)
{
...
var res = await fetch(...); //activate the remote proccess
var textAnswer = await res.text();
var infoObj = JSON.parse(textAnswer);
startChecks(infoObj.info.id); // this is the method which I need to await on somehow
}("paramValue");
async function startChecks(id)
{
var startTime = new Date().getTime();
intervalId = setInterval(checkStatus, 5000, id, startTime);
}
async function checkStatus(id, startTime)
{
//if more than 5 minutes had passed
if(new Date().getTime() - startTime > 300000)
{
clearInterval(intervalId);
throw new Error("External pipeline timeout expired");
}
var res = await fetch(...); //check remote process
var ans = await res.text();
var obj = JSON.parse(ans);
if(obj.finished) clearInterval(intervalId);
}
Like I said, what I want to achieve is that my main function won't end until all intervals are done with either the error thrown or the process finishes. How can I achieve that?
You would create a helper function that executes your function in given intervals until it resolves to something different than undefined. But only for the maximum amount of time.
This could look something like this:
// helper to wait for time milliseconds
async function sleep(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function repeatedExecution(callback, interval, timeout) {
// get start time
const start = Date.now();
// repeat as long the start time + timout is larger then the current time
while (start + timeout > Date.now()) {
// get a promise that resolves in interval seconds
let sleeper = sleep(interval)
// execute the callback
let res
if (res = await callback()) {
// if the callback returns something truthy return it
return res;
}
// wait until time for interval ends and then continue the loop
await sleeper;
}
// if we reach this point we timed out
throw new Error('timed out');
}
async function run() {
/*
var res = await fetch(...); //activate the remote proccess
var textAnswer = await res.text();
var infoObj = JSON.parse(textAnswer);
*/
try {
let result = await repeatedExecution(() => {
/*
var res = await fetch(...); //check remote process
var ans = await res.text();
var obj = JSON.parse(ans);
if(obj.finished) {
return true
}
*/
}, 1000, 3000);
// do something on success
} catch (err) {
// handle the error (timeout case)
console.error(err)
}
}
run();

Make page display 10 seconds in a loop

I'm building a guessing game with Node JS. After collecting some data on the back-end, I send it to the front-end and the game starts. The data contains all 10 levels, so the game can run on a single page. Each level runs for 10 seconds. After the time is up, the user selection is sent to the server, and a result comes back. The answer is displayed, and then the content is changed to the "next level" (using the content in the big data object, therefore no refresh is needed).
I'm having some issues with having 10 levels run for 10 seconds each (or ~12 seconds with a delay for displaying the results).
This can't be done in some type of loop, since all awaits for each level will run at once. For instance:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
for (let i = 0; i < 10; i++) {
displayPage(i);
await timeout(10000);
const result = await $.post(...) // gets results
displayResults(result);
await timeout(2000);
}
all the timeouts will run at once, and it won't work.
I thought of using a setInterval, but I'm not sure how to.. since I want to wait 10 seconds until checking the input, and then display the results for 2 seconds, and then move on.
For now the result I came up with is:
displayPage(level1);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
displayPage(level2);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
displayPage(level3);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
displayPage(level4);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
...
This does not seem efficient and I think there's a better way to do this, but I'm not sure how.
Any suggestions would be appreciated. Thanks!
I think this is what you are looking for:
const pages = [1, 2, 3, 4, 5];
run();
async function run() {
for (let i = 0; i < pages.length; i++) {
await displayPage(i);
const result = 'Some result';
await displayResult(result);
}
}
function displayPage(number) {
text.innerText = 'Page ' + number;
return new Promise(res => {
setTimeout(res, 10000);
});
}
function displayResult(result) {
text.innerText = 'Result: ' + result;
return new Promise(res => {
setTimeout(res, 2000);
});
}
<div id="text"><div>
Another solution, without promises and loops:
const pages = [1, 2, 3, 4, 5];
let currentPageIndex = 0;
displayPage();
function displayPage() {
const index = currentPageIndex++;
if (pages[index] === undefined) return;
const pageNumber = pages[index];
text.innerText = 'Page ' + pageNumber;
const result = 'Some result';
setTimeout(() => {
displayResult(result);
}, 10000);
}
function displayResult(result) {
text.innerText = 'Result: ' + result;
setTimeout(() => {
displayPage();
}, 2000);
}
<div id="text"></div>
Your first option seems to work assuming it is wrapped within an async function:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const testFunc = async () =>{
for (let i = 0; i < 10; i++) {
console.log('page' , i+1 , '- question')
await timeout(3000);
console.log('page' , i+1 , '- answer')
await timeout(1000);
}
}
testFunc()
use setInterval on about 1000ms to create worker and add a state-machine that toggles the playing(10s) and the waiting(2s). You need a procedure that does the post call to the server and an object to keep the data(levels and stuff).
for example:
setInterval(funcWorker,1000,s_gameObj);
function funcWorker(f_context){
var l_dateNow = new Date();
if(f_context.is_playing){
var l_elapsed = l_dateNow.getTime() - f_context.dateLevelStart.getTime();
if(l_elapsed.totalSeconds() >= f_context.LEVEL_TIME){
f_context.end_level();
}
}else if(f_context.is_waiting_user){
//same check as above but on the waiting start
....
f_context.next_level();
}else if(f_context.is_waiting_server){
//do whatever
}
}
the end_level() should set the state flag in the context(game) object, to sending and send to the server. As the server returns in the response function set the state to waiting user and init the corresponding time variable to now(). The next_level() should set the state to playing and init the corresponding time variable to now() so that the timer can count. Consider the code above as a reference and not as a copy-paste resource.

Why does the frequency of my received websockets sometimes equal zero?

Im reciving websocket messages on my webpage, and i'm trying to calculate the frequency at which they are received. I do this like so:
let startTime = new Date();
ws.onmessage = function (evt)
{
prevData = recivedData;
var receivedMsg = evt.data;
recivedData = JSON.parse(receivedMsg);
const endTime = new Date();
const timeDif = endTime - startTime;
startTime = endTime;
console.log(timeDif)
}
When I take a look at the console to see what it has printed, I see that the frequency is mostly around 60 ms (as expected). Although, every fourth time or so, it will be 0 ms, I find this unlikely and I cant find what is causing it. Any ideas on how to fix this issue?
ws.onmessage = async () => {
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
await wait(50); // await between executions
// do your code
}
Update:
See this sample code below. You said that you have some loop function. Maybe it will be helpful you if you have access to the event queue every cycle and never have more than one event in the queue.
let done = false;
setTimeout(() => {
done = true
}, 5);
const eventLoopQueue = () => {
return new Promise(resolve =>
setImmediate(() => {
console.log('event loop');
resolve();
})
);
}
const run = async () => {
while (!done) {
console.log('loop');
await eventLoopQueue();
}
}
run().then(() => console.log('Done'));
What happens here. You call eventLoopQueue() function every cycle. This function pushes the callback to the queue by setImmidiate(). The callback is executed right away and anything that is in the queue are going to be executed as well. So every cycle you clear up the queue. And I believe it will help you.

Categories

Resources