I’m trying to solve a simple problem here but I have no idea what direction to take.
getAuthNumber() // returns a promise with a number (eg 98765)
// response times can be 5s-20s
<div class=“auth”> </div>
//code
let counter = 0;
let el = document.getElementsbyClassName(“auth”)[0];
let func = setInterval(function(){
counter++;
getAuthNumber().then((num)=>{
return [num, counter];
}).then(res){
If(counter == res[1])
el.innerHTML = res[0];
}, 10000);
I need to write a function that gets the auth number every 10s & displays it in the block below. I’ve tried using set interval but getAuthNumber() can take more than 10s to return in which case, I need to discard that response and only show the current value.
Do not use setInterval instead use setTimeOut function.
setInterval execution depends on the CPU usage, if it in case increased the setinterval will not gets completed in the interval specified
What you can do is run an async function inside setInterval. In the following code snippet, the getAuth function completes after 2s but setInterval runs every 1s. But it still works because there is an async funciton inside setInterval.
const getAuth = () => {
return new Promise((res, rej) => {
setTimeout(() => res(Math.random()), 2000);
});
};
const setDiv = async () => {
const res = await getAuth();
console.log(res);
};
setInterval(setDiv, 1000);
I have adapted this gist by Jake Archibald (see JavaScript counters the hard way - HTTP 203) into the following code:
function promiseInterval(milliseconds, signal, promiseFactory, callback) {
const start = performance.now();
function tick(time) {
if (signal.aborted){
return;
}
promiseFactory().then(
value => {
callback(value);
scheduleTick(time);
}
);
}
function scheduleTick(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / milliseconds) * milliseconds;
const targetNext = start + roundedElapsed + milliseconds;
const delay = targetNext - performance.now();
setTimeout(tick, delay);
}
scheduleTick(start);
}
Starting from the gist, I have removed the use of requestAnimationFrame and document.timeline.currentTime (using only performance.now), and I have added the promiseFactory parameter, plus some renaming (animationInterval renamed to promiseInterval, ms renamed to milliseconds and scheduleFrame renamed to scheduleTick) and formatting.
You would use it like this:
const controller = new AbortController(); // This is used to stop
promiseInterval(
10000, // 10s
controller.signal, // signal, to stop the process call `controller.abort`
getAuthNumber, // the promise factory
num => {el.innerHTML = num;} // this is what you do with the values
);
It will not really call getAuthNumber each 10 seconds. Instead, it will wait until getAuthNumber completes and schedule to call on the next 10 seconds interval, and repeat. So it is not calling it multiple times and discarding values.
Related
I try to implement a loop in my noejs app that will always wait between the tasks. For this I found the setInterval function and I thought it is the solution for me. But as I found out, the first Interval, means the very first action also wait until the interval is ready. But I want that the first action runs immediatly and then each action with the given interval.
In arry scope:
myArray[0] starts immediatly while myArray[1..10] will start with Interval waiting time.
I tried it with:
function rollDice(profilearray, browserarray, url) {
return new Promise((resolve, reject) => {
var i = 0;
const intervalId = setInterval(
(function exampleFunction() {
console.log(profilearray[i].profil);
//########################################################################
createTestCafe("localhost", 1337, 1338, void 0, true)
.then((tc) => {
testcafe = tc;
runner = testcafe.createRunner();
inputStore.metaUrl = url;
inputStore.metaLogin = teamdataarray[0].email;
inputStore.metaPassword = teamdataarray[0].password;
inputStore.moderator = profilearray[i].profil;
inputStore.message = profilearray[i].template;
inputStore.channelid = profilearray[i].channelid;
})
.then(() => {
return runner
.src([__basedir + "/tests/temp.js"])
.browsers(browserarray)
.screenshots("", false)
.run()
.then((failedCount) => {
testcafe.close();
if (failedCount > 0) {
console.log(profilearray[i].profil);
console.log("No Success. Fails: " + failedCount);
//clearInterval(intervalId);
//reject("Error");
} else {
console.log(profilearray[i].profil);
console.log("All success");
//clearInterval(intervalId);
//resolve("Fertig");
}
});
})
.catch((error) => {
testcafe.close();
console.log(profilearray[i].profil);
console.log("Testcafe Error" + error);
//clearInterval(intervalId);
//reject("Error");
});
//######################################################################
i++;
console.log("Counter " + i);
if (i === profilearray.length) {
clearInterval(intervalId);
resolve("Fertig");
}
return exampleFunction;
})(),
3000
); //15 * 60 * 1000 max time to wait (user input)
});
}
The way I have done works bad because in the first action it will not start the testcafe. But in all other actions it will do.
Anybody knows a better way to do this?
Scope:
Give a array of data and for each array start testcafe with a given waiting time. 3 seconds up to 15 minutes. Because in some cases 15 Minutes is a long time I want to start the first one without any waiting time.
Iam open for any suggestion
For modern JavaScript await and async should be used instead of then and catch.
This will make many things easier, and the code becomes more readable. You e.g. can use a regular for loop to iterate over an array while executing asynchronous tasks within it. And use try-catch blocks in the same way as you would in synchronous code.
// a helperfunction that creates a Promise that resolves after
// x milliseconds
function wait(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function rollDice(profilearray, browserarray, url) {
for (let i = 0; i < profilearray.length; i++) {
// depending how you want to handle the wait you would create
// the "wait"-Promise here
// let timer = wait(3000)
let testcafe = await createTestCafe("localhost", 1337, 1338, void 0, true);
try {
let runner = testcafe.createRunner();
inputStore.metaUrl = url;
inputStore.metaLogin = teamdataarray[0].email;
inputStore.metaPassword = teamdataarray[0].password;
inputStore.moderator = profilearray[i].profil;
inputStore.message = profilearray[i].template;
inputStore.channelid = profilearray[i].channelid;
let failedCount = await runner.src([__basedir + "/tests/temp.js"])
.browsers(browserarray)
.screenshots("", false)
.run()
if (failedCount > 0) {
// ...
} else {
// ...
}
} catch (err) {
console.log(profilearray[i].profil);
console.log("Testcafe Error" + error);
} finally {
testcafe.close();
}
// Here you would wait for the "wait"-Promise to resolve:
// await timer;
// This would have similar behavior to an interval.
// Or you wait here for a certain amount of time.
// The difference is whether you want that the time the
// runner requires to run counts to the waiting time or not.
await wait(3000)
}
return "Fertig"
}
Declare function before setInterval, run setInterval(exampleFunction, time) and then run the function as usual (exampleFunction()). May not be ideal to the millisecond, but if you don't need to be perfectly precise, should work fine.
Ask if you need further assistance
EDIT: now looking twice at it, why are you calling the function you pass as parameter to setInterval inside the setInterval?
I am trying to use setInterval to call an API that has rate limit for requests per seconds.
I tried to use setInterval as a while loop, but I can't find info on when it starts the next round.
So if I do:
setInterval(() => {
CallAPI();
// Some proccessing
}, 300)
Will the next round start after the first one finishes or regardless of it? because order matter, and also timing can cause an error.
I can also try to do the same with setTimeout and a while loop like so:
while(true) {
setTimeout(() => {
CallAPI();
// do some proccessing
}), 300
}
But again, I am not sure when the next round of the loop will start.
And lastly I cheat my way through it and do
while(true) {
await CallAPI();
// Do proccessing
await setTimeout(() => true, 300)
}
So what is the order for setTimeout() and setInterval() in a loop?
Just call it when it is done executing. No need for a loop.
function fakeCall () {
return new Promise((resolve) => {
window.setTimeout(resolve, Math.random() * 2);
});
}
function next() {
window.setTimeout( async function () {
await fakeCall();
console.log(Date.now());
next();
}, 3000);
}
next();
You'll probably want to set up your loop as follows:
var TO_asyncLoop = null;
function asyncLoop(fn, ms){
clearTimeout(TO_asyncLoop);
TO_asyncLoop = setTimeout(() => { fn() }, ms)
}
var i = 0;
function doSomething(){
// do somthing
console.log("hi", i++);
// when this "something" is finished, do it again:
asyncLoop(doSomething, 500)
}
doSomething()
I currently have the following loop
for (const pk of likerpk)
{
await likeMediaId(ig, pk.trim());
}
the issue is that i wanted to call likeMediaId every X seconds and after the whole likeMediaId is done I wanted to call the function called Y
I have tried putting a sleep inside a for loop but that does not help, it seems that it is still executing likeMediaId in parallel, while i always want to execute 1 likeMediaId at a time
basically the flow that i wanted is (assuming 3 items in the array)
likeMediaId()
sleep 60 seconds
likeMediaId()
sleep 60 seconds
likeMediaId()
sleep 60 seconds
call function Y
what is the most elegant way in doing this?
if you're open to rxjs:
const likeMediaId$ = (pk) => from(likeMediaId(ig, pk.trim())); // observable from promise
from(likerpk).pipe( // observable stream from list items
concatMap(item => of(item).pipe(delay(60000))) // wait 60 seconds
switchMap(pk => likeMediatId$(pk)), // run the function
finally(() => Y()) // at end, do this
).subscribe(
result => console.log(result),
error => console.log(error)
)
wana go super vanilla? why not try an interval?
let i = likerpk.length // counter
const clearInterval = setInterval(() => {
i -= 1 // iterate
await likeMediaId(likerPk[i]) // await (runs backwards)
if (!i) { // out of items
clearInterval() // clear
Y() // run Y
}
}, 60000) // every 60 seconds
you can do it with a little helper:
// a little utility
const delay = (delay) => new Promise(resolve => setTimeout(resolve, delay));
for (const pk of likerpk)
{
await likeMediaId(ig, pk.trim());
await delay(60000);
}
Y();
it seems that it is still executing likeMediaId in parallel
Then this is an error in your function likeMediaId. It seems to resolve before the work is done. That's the only way all iterations could run basically in paralell.
you could use a timeout to do it. Maybe it's not the most elegant, but should work fine.
var i=0;
async function cycle() {
await likeMediaId(ig, likerpk[i].trim());
i++;
if (i < likerpk.length) {
setTimeout(cycle, 1000);
}
}
cycle();
How can I use RxJS to buffer function calls until another async function has resolved?
Here is a simple example of what I'd like to accomplish
function asyncFunc(time) {
setTimeout(() => {
console.log('asyncFunc has resolved');
}, time);
}
function funcToBuffer(time) {
setTimeout(() => {
console.log(time);
}, time);
}
asyncFunc(3000);
funcToBuffer(1000);
funcToBuffer(2000);
funcToBuffer(4000);
funcToBuffer(5000);
asyncFunc(8000);
funcToBuffer(6000);
funcToBuffer(7000);
At the moment this code will print:
1000
2000
asyncFunc has resolved
4000
5000
6000
7000
asyncFunc has resolved
What I want is for this to print:
asyncFunc has resolved
1000
2000
4000
5000
asyncFunc has resolved
6000
7000
In essence, I want some kind of control flow that allows me to call funcToBuffer whenever I feel like, but under the hood, I want it to hold on executing whenever asyncFunc is executing and waiting to be resolved. Once asyncFunc has resolved, funcToBuffer calls should no longer be buffered and be executed right away.
I have tried playing with the buffer operator but I wasn't able to achieve the desired outcome.
If I understand it right, your main goal is to control the execution of a sequence of functions through a mechanism that buffers them until something happens, and that something is exactly what triggers the execution of the functions buffered.
If this is correct, the following could be the basis for a possible solution to your problem
const functions$ = new Subject<() => any>();
const buffer$ = new Subject<any>();
const executeBuffer$ = new Subject<any>();
const setBuffer = (executionDelay: number) => {
buffer$.next();
setTimeout(() => {
executeBuffer$.next();
}, executionDelay);
}
const functionBuffer$ = functions$
.pipe(
bufferWhen(() => buffer$),
);
zip(functionBuffer$, executeBuffer$)
.pipe(
tap(functionsAndExecuteSignal => functionsAndExecuteSignal[0].forEach(f => f()))
)
.subscribe();
Let me explain a bit the code.
First thing, we build functions$, i.e. an Observable of the functions we want to control. The Observable is built using a Subject, since we want to be able to control the notification of such Observable programmatically. In other words, rather than kicking the execution of a function like this funcToBuffer(1000), we create the function (as an object) and ask the functions$ Observable to emit the function like this
const aFunction = () => setTimeout(() => {console.log('I am a function that completes in 1 second');}, 1000);
functions$.next(aFunction);
In this way we have created a stream of functions that eventually will be executed.
Second thing, we create 2 more Observables, buffer$ and executeBuffer$, again using Subjects. Such Observables are used to signal when we have to create a buffer out of the functions emitted so far by functions$ and when we have to start the execution of the functions buffered.
These last 2 Observables are used in the function setBuffer. When you call setBuffer you basically say: please, create a buffer with all the functions which have been emitted so far by functions$ and start executing them after the executionDelay time specified as parameter.
The buffering part is performed by the functionBuffer$ Observable which is created using bufferWhen operator. The execution part is implemented leveraging the zip operator, that allows us to set the rhythm of execution of the functions based on the emissions of executeBuffer$ Observable.
You can test the above code setting up the following test data.
let f: () => any;
setBuffer(3000);
f = () => setTimeout(() => {console.log('f1');}, 1000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f2');}, 2000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f4');}, 4000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f5');}, 5000);
functions$.next(f);
setBuffer(8000);
f = () => setTimeout(() => {console.log('f6');}, 6000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f7');}, 7000);
functions$.next(f);
setBuffer(16000);
I started working on a solution with combineLatest but figured that a BehaviorSubject would be a better solution once I put more thought into it.
const { BehaviorSubject } = rxjs;
const { filter } = rxjs.operators;
let finalised$ = new BehaviorSubject(false);
function asyncFunc(time) {
setTimeout(() => {
console.log('asyncFunc has resolved');
if (!finalised$.getValue()) {
finalised$.next(true);
}
}, time);
}
function funcToBuffer(time) {
finalised$.pipe(filter(finalised => finalised)).subscribe(_ => { // Filter so only fire finalised being true
setTimeout(() => {
console.log(time);
}, time);
});
}
asyncFunc(3000);
funcToBuffer(1000);
funcToBuffer(2000);
funcToBuffer(4000);
funcToBuffer(5000);
asyncFunc(8000);
funcToBuffer(6000);
funcToBuffer(7000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>
CombineLatest that waits for both obsevables to fire.
const { of, combineLatest } = rxjs;
const { delay } = rxjs.operators;
let obs1$ = of(1).pipe(delay(1000));
let obs2$ = of(2).pipe(delay(2000));
let now = new Date();
combineLatest(obs1$, obs2$).subscribe(([obs1, obs2]) => {
let ellapsed = new Date().getTime() - now.getTime();
console.log(`${obs1} - ${obs2} took ${ellapsed}`);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>
I've a web component for auto-logout functionality which shows modal window with a message on 59th minute and stay for another minute in case of no activity. And logs out the user if user doesn't click anywhere on the window. So, no activity for an hour will logout the user automatically. This works fine.
Now, to test this functionality, I tried to use sinonjs. I used FakeTimers but couldn't able to achieve the result. I am trying to test that modal window with message shows up.
Here's the code:
const { When } = require('cucumber'); // eslint-disable-line import/no-extraneous-dependencies
const fs = require('fs');
const path = require('path');
let clock;
async function setupSinon() {
const sinonPath = require.resolve('sinon');
const content = await new Promise((resolve, reject) => {
fs.readFile(
path.join(sinonPath, '../../pkg/sinon.js'),
'utf-8',
async (error, cont) => {
if (error) return reject(error);
resolve(cont);
},
);
});
// creating <script> element for sinonjs to execute on the page
await browser.execute((content) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.text = content;
document.head.appendChild(script);
}, content);
}
async function iWaitForNMinutes() {
await setupSinon();
await browser.execute(() => {
before(() => {
clock = sinon.useFakeTimers();
});
clock = sinon.useFakeTimers({
now: Date.now(),
shouldAdvanceTime: true,
toFake: ['setTimeout'],
});
clock.tick('59:00'); // advancing the clock to 59 minutes so that auto-logout modal window popup, but this doesn't work
after(() => {
clock.restore();
});
setTimeout(() => {}, 60000);
});
}
When(/^I wait for minutes$/, iWaitForNMinutes);
module.exports = {
iWaitForNMinutes,
};
sinon 5.0.10
How to user sinonjs FakeTimer to advance the time to n minutes and then wait actually for n minutes ?
Sinon's fake timers are pretty easy to work with and are my favourite feature of sinon.
The usage goes like this
In your code
// in your code
eatCake() {
setTimeout(function() {
// eat cake after 10s
}, 10000); // 10000 === 10s
}
In your test
clock = sinon.useFakeTimers();
clock.tick(9000);
// check if cake is eaten - answer will be false
clock.tick(1000); // 9000 + 1000 === 10s
// check if cake is eaten - answer will be true
So sinon basically fast forwards (programatically) the timer so our test can check for the desired result without actually waiting the time because all test frameworks usually have a wait timeout of 2s after which a test case will fail.
In your case, to wait for 59 minutes, you could write
clock.tick(1000 * 60 * 59); // 59 minutes
// check if the modal has opened up
clock.tick(1000 * 60 * 1); // 1 minute
// check if the user is logged out
And don't forget to restore the clock at the end as you've already done.
clock.restore();
Your general problem is probably in the await before your function (browser.execute):
advancing the clock before await is pointless (regarding anything like setTimeOut inside)... since any of those inner timing functions is not yet set up.
advancing the clock after the await <function> is pointless because indeed, the await will wait...
The solution is to not use await with your (async) function but treat it like any other promise-returning function (which any async function actually is).
it('some test', async () => {
await new Promise((resolve, reject) => {
// normally used with await
functionToBeTested(...some params).then(
(value) => {
log('now it happend')
resolve()
},
(reason) => {
reject(reason)
}
)
// by not using await (despite deleteAction being an sync function)
// but rather just chaining a then, this clock.tick gets run while
// deleteAction is "running" resp. waiting for some timeout:
clock.tick(1000)
})
... some more asserts on the result...
(we can be rest-assured the command under test is completed here)
})