How to run an inline-function twice? - javascript

I'm using Gametime.js to make a world chat in real time.
Messages are stored in a database.
Gametime.js uses PubNub and for some reason PubNub needs the message sent twice so it actually does it.
How can I make a function run twice?
I've tried this:
for (let i = 0; i < 2; i++) { gametime.run("msg", [msg]) }
And it works, it's just that I do this very often in my script, so is there a way to do it without a for/while loop?
Here's an example of what I'd like to achieve:
// inline code, cannot run for loop right here
function example(msg) { doSomething(), doSomethingElse, {{run twice}}, done() }

You can create another function which will run your function twice, like this:
function twice (callback) {
callback();
callback();
}
twice(() => console.log('hello'));
However, if you're experiencing a scenario where you are having to invoke a function twice to get the desired result, it sounds like there's another problem somewhere (in some code that you didn't show).

Try overwriting the run method, sounds like calling the method twice is a bug and this will make it easy to back out if it gets fixed.
gametime.oldRun = gametime.run;
gametime.run = (...params) => {
this.oldRun(...params);
this.oldRun(...params);
};
It could cause problems if internal methods call run so you could create run2
gametime.run2 = (...params) => {
this.run(...params);
this.run(...params);
};
Still easy to back out with a find and replace.

Related

Using for loop and if else in cypress

I am in a situation where I need to use for loop and If else block in cypress
Scenario:
Once I login to an application, I need to read an element's text which is rounded in the below screenshot.
This element will appear within 20-90 seconds after I log in, when I refreshed the screen. so I need to write something like this, wait for element, if it appears reads the text and returns the value, if not wait for 10 seconds reload the page and do the process again.
function waitAndreload() {
for (let i = 0; i < 10; i++) {
cy.get("#ele").then(ele => {
if (ele.text()) {
return ele.text();
} else {
cy.wait(10000);
cy.reload();
}
});
}
}
How to write this in cypress, as cypress won't support if-else or for loops
Below is a technical solution, but first is want to explain what I believe is a better solution.
Adding to #dwelle comment, it seems like what you're trying to do is not a best-practice in terms of the the design of the test. Tests should be designed to be deterministic and should control all relevant inputs that may affect the expected result.
More specifically, is this text something that a real user should see and use, or only something that the developers put for debugging or testing purposes? If it's for a real user, then what determines if it should appear or not? (Is it purely random?, If so, see below) if it's for testing or debugging purposes, talk to the developers and come up with a better solution in which you can control whether this text appears or not directly. If it's something that the user should see and it's not random, then consider what conditions should be met in order for the text to appear, and design the test in a way that makes this condition true, either by controlling the actual necessary preconditions or by using mocks to simulate that condition. Again, I recommend that you consult with the developers to help you find the best approach.
In case it is "purely" random, then ask the developers to provide a way for you to specify the seed of the random generator, and then you'll be able to control it too.
As promised, in case you still want the technical solution for the specific problem, without redesigning the test, then there trick is to use recursion. Something like this:
function getEnvironment() {
function getEnvironmentInternal(retires) {
if (retires == 0)
throw "text didn't appear after the specified retires";
return ele.text().then(text => {
if(text)
return cy.wrap(text);
cy.wait(10000);
cy.reload();
return getEnvironmentInternal(retires-1);
});
)};
return getEnvironmentInternal(10);
}
// usage:
getEnvironment().then(text => {
// do something with text...
}
I wrote my own helper command for checking repeatedly until condition is fulfilled.
/**
* Waits until call to cb() resolves to something truthy.
*
* #param message {string} Error message on timeout
* #param cb {() => Cypress Command "promise"}
* Callback for checking if condition is met should return cypress command cy.xxxxxx.then()
* which resolves to undefined if polling should continue. Throwing an error aborts before
* waiting for timeout to complete.
*/
Cypress.Commands.add('waitFor', (message, cb, errorReporterCb = null, timeoutMs = 5000) => {
const startTime = new Date().getTime();
const giveupTime = startTime + timeoutMs;
const startTimeout = 5;
const ctx = {};
const errorReporter =
errorReporterCb ||
(err => {
throw err;
});
function checkCb(timeout) {
const currentTime = new Date().getTime();
if (currentTime > giveupTime) {
const err = new Error(`Timeout while waiting for (${currentTime - startTime}ms): ${message}`);
errorReporter(err, ctx);
} else {
cy.wait(timeout);
return cb(ctx).then(result => {
if (result === undefined || result === false) {
return checkCb(timeout * 2); // always wait twice as long as the last time
} else {
return result;
}
});
}
}
return checkCb(startTimeout);
});
With this you can implement polling loop like:
cy.waitFor(
'reload page until #ele contain text',
() => cy.reload().get("#ele").then(ele => ele.text() ? ele.text() : undefined),
null, 60000);
I would say using a for loop for something like this and refreshing is an anti-pattern. It looks like you're waiting for the text to show up in the element, not the element itself.
If so, can you stub the response to the server so it comes back right away? If that doesn't work, just do a cy.wait('#<whatever you aliased your response as>') until the call is completed
So it seems you just want to wait that element is appeared and then take text value.
So something like cy.get('#ele', {timeout: 60000}).should('exist').invoke('text').then(text => ...work with text value)
Assertions in cypress have built-in retry mechanism, so if it fails before timeout expire - it will retry previous command.
You can't use while/for loops with cypress because of the async nature of cypress. Cypress doesn't wait for everything to complete in the loop before starting the loop again. You can however do recursive functions instead and that waits for everything to complete before it hits the method/function again.
Here is a simple example to explain this. You could check to see if a button is visible, if it is visible you click it, then check again to see if it is still visible, and if it is visible you click it again, but if it isn't visible it won't click it. This will repeat, the button will continue to be clicked until the button is no longer visible. Basically the method/function is called over and over until the conditional is no longer met, which accomplishes the same thing as a loop, but actually works with cypress.
clickVisibleButton = () => {
cy.get( 'body' ).then( $mainContainer => {
const isVisible = $mainContainer.find( '#idOfElement' ).is( ':visible' );
if ( isVisible ) {
cy.get( '#idOfElement' ).click();
this.clickVisibleButton();
}
} );
}
Then obviously call the this.clickVisibleButton() in your test. I'm using typescript and this method is setup in a class, but you could do this as a regular function as well.

Utilizing setTimeout to alter Call Stack order

I have an asynchronous function running in my web application that enables a chat input. In a different component I need to set a variable to the input and then focus() on it when certain conditionals are met. Unfortunately the chat input DOM element isn't always available when I try to declare it based on the asynchronous nature of the function that enables it. Being familiar with how setTimeoout() works with the call stack I wrapped my declaration in a setTimeout and everything (seemingly) works as expected now.
So my question is if this is a good practice or not? I'm using React/Redux and will have to do a lot of prop threading and extra logic to get a seemingly easy task accomplished without the setTimeout.
It is an alright practice ;)
It gets the job done, but it is usually preferable to work with callbacks or promises instead of polling to see if the dom is ready. The main failing with a "setTimeout" approach is that you are setting a timer and what if the resource (chat plugin) takes longer to load than the timer you set.
// Run it
main();
// Supporting code
function main() {
let attempts = 0;
const maxAttempts = 10;
tryUpdate();
function tryUpdate() {
// Call it once
attempts++;
const success = updateAndFocus();
console.log(attempts);
// Keep calling it every 100ms
if (!success && attempts < maxAttempts) {
setTimeout(() => tryUpdate(), 100);
}
}
}
function updateAndFocus() {
const el = document.getElementById('findme');
if (!el) return false;
// do work
el.focus;
return true;
}

Ignore multiple successive function calls in Typescript from within the function?

I am trying to implement autocomplete using Handontable (Angular) by fetching data from the server as the user types.
I notice the API calls are made every time the user input changes but I would like to limit the number of API calls by waiting 1 second for the user to stop typing before making the call.
I have done this in the past when I controlled the event by using debounceTime but not sure how to implement that here.
...
column.source = function(query, callback) {
$component.dataService.getValidValues().subscribe(
arg => {
callback(arg);
},
err => {
...
}
);
}
...
Adding debounceTime(1000) here doesn't prevent multiple calls from happening.
$component.dataService.getValidValues().debounceTime(1000).subscribe(...)
As already explained by others you need to debounce the input. In your case this would be the invocation of the function.
One way to achieve that is using a subject that you create somewhere in your code:
const sourceRequest = new Subject();
sourceRequest.debounceTime(1000).subscribe(callback => {...});
The code you currently have inside function(query, callback) { goes into subscribe. The column definition is then changed to this:
column.source = function(query, callback) {
sourceRequest.next(callback);
}
Assuming that .getValidValues() is the function that makes the request to the remote server, you are debouncing the stream of events coming from this function. You want to debounce the stream of events coming from the user input, which limits the number of calls to .getValidValues().
Try something like this
$component.dataService.debounceTime(1000).getValidValues().subscribe(...)
The only thing I can think of is for you to create a standard Javascript debounce function that wraps the function you are assigning to column.source, kind of like this:
https://stackblitz.com/edit/angular-mq9jyv?file=src%2Fapp%2Fapp.component.ts

Delayed Ajax calls to same URL using same data

While waiting for the back end devs to implement a "cancel all" feature, which cancels all tasks tracked by the back end, I am attempting to makeshift it by cancelling each individual task. The cancel REST service accepts an ID in the form of a data object {transferID: someID}.
I use a FOR loop to iterate over an array of IDs that I have stored elsewhere. Anticipating that people MAY end up with dozens or hundreds of tasks, I wanted to implement a small delay that will theoretically not overflow the number of HTTP requests the browser can handle and will also reduce a blast of load on the back end CPU. Here is some code with comments for the purpose of this discussion:
ta.api.cancel = function (taskArray, successCallback, errorCallback) {
// taskArray is ["task1","task2"]
// this is just the latest attempt. I had an attempt where I didn't bother
// with this and the results were the same. I THOUGHT there was a "back image"
// type issue so I tried to instantiate $.ajax into two different variables.
// It is not a back image issue, though, but one to do with setTimeout.
ta.xhrObjs = ta.xhrObjs || {};
for (var i = 0; i < taskArray.length; i++) {
console.log(taskArray); // confirm that both task1 and task2 are there.
var theID = taskArray[i];
var id = {transferID: theID}; // convert to the format understood by REST
console.log(id); // I see "task1" and then "task2" consecutively... odd,
// because I expect to see the "inside the setTimeout" logging line next
setTimeout(function () {
console.log('inside the setTimeout, my id is: ')
console.log(id.transferID);
// "inside the setTimeout, my id is: task2" twice consecutively! Y NO task1?
ta.xhrObjs[theID] = doCancel(id);
}, 20 * i);
}
function doCancel(id) {
// a $.Ajax call for "task2" twice, instead of "task1" then "task2" 20ms
// later. No point debugging the Ajax (though for the record, cache is
// false!) because the problem is already seen in the 'setTimeout' and
// fixed by not setting a timeout.
}
}
Thing is: I know setTimeout makes the containing function execute asynchronously. If I take out the timeout, and just call doCancel in the iterator, it will call it on task1 and then task2. But although it makes the call async, I don't understand why it just does task2 twice. Can't wrap my head around it.
I am looking for a way to get the iterator to make the Ajax calls with a 20ms delay. But I need it to call on both! Anybody see a glaring error that I can fix, or know of a technique?
You must wrap your function setTimeout and pass the id variable into it, like this:
(function(myId, i) {
setTimeout(function () {
console.log('inside the setTimeout, my id is: ', myId);
}, 20 * i);
}(theId, i));
This pattern does not create a unique variable1 for each instance of the loop as one might expect.
function () {
for (var i = 0; i < length; i++) {
var variable1;
}
}
In javascript variables are "hoisted". To quote Mozilla:
"Because variable declarations (and declarations in general) are
processed before any code is executed, declaring a variable anywhere
in the code is equivalent to declaring it at the top."
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
So it should be re-written as:
function () {
var variable1;
for (var i = 0; i < length; i++) {
}
}
What this means is that after the loop has finished, any asynchronous callbacks that reference this variable will see the last value of the loop.

How to let JavaScript wait until certain event happens?

I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.

Categories