When using waitForKeyElement.js I was wondering if there is a good solution to implementing a max time to wait? Sometimes I have elements that are supposed to be there, but sometimes on a poor ajax response they do not appear, or they may take a while to appear. In cases like that I simply want it to move on and execute a separate function.
Edit: For example, when trying to "buy" an item it typically always responds with a "success" box which is what the program is waiting for, however sometimes this box does not happen if the server has an error or if the item is no longer available to "buy" in which case a different error element may be inserted instead, however this is a different element and as such it continues to wait instead of continuing on to process the next item.
The question does not have an example use case, nor MCVE. There's a good chance that it is an XY Problem.
That said, there's no elegant way to set a max wait time (because there's never been neither a need, nor a demand for one before).
You can get the effect with code like:
const maxTime = 5; // seconds
var watchdogTimer = setTimeout (fallBackFunc, maxTime * 1000);
waitForKeyElements ("#foo", desiredFunc, true);
function desiredFunc (jNode) {
console.log ("Foo found!");
clearTimeout (watchdogTimer);
}
function fallBackFunc () {
console.log ("Oh, poo. No foo.");
}
Unloading the waitForKeyElements timer in such a scenario should not be necessary, but you can also do that by adding the following code at the end of fallBackFunc():
function fallBackFunc () {
console.log ("Oh, poo. No foo.");
/*-- Optional body double to take fire from waitForKeyElements that the
has `true` parameter set. This is almost never needed.
*/
var nonce = "IdeallySomeGloballyUniqueStringThatIs_a_ValidCssClass";
//-- Added node should have properties that match the WFKE selector
$("body").append (`<span id="foo" class="${nonce}" style="display:none;">blah</span>`);
setTimeout (function () {$(`.${nonce}`).remove(); }, 333); // time must be > 300
}
Regarding the question edit:
... (the page) responds with a "success" box which is what the program is waiting for, however sometimes this box does not happen... in which case a different error element may be inserted instead...
The usual practice for that is to set waitForKeyElements to listen for both the success node and the error node. (See jQuery selectors doc.)
The waitForKeyElementscallback would then take the appropriate action for the type of node passed to it.
No watchdog timer is needed (unless the page can stay active and yet not return either node type -- which is almost never the case).
For example:
waitForKeyElements ("#goodNode, #errorNode", completeTransaction, true);
function completeTransaction (jNode) {
if (jNode.is ("#goodNode") ) { // Match one of the WFKE selectores
console.log ("Transaction success!");
}
else {
console.log ("Transaction error.");
}
}
As a bonus, this approach: (A) doesn't have to wait for a max timer to run out and (B) is not subject to breaking if max timer is not set long enough for every circumstance.
Related
I am trying to write tow If Statements in which, if the conditions are coming alternately then my code is working fine.
But when both Statements are true together then the second If Statement always skipped because there is a time difference of 5-6 seconds after the first condition executed.
So even though second condition is valid ( waiting for a window to appear) it never went inside the second if-Statement.
I tried writing timeout but it didn't work in TestCafe.
Someone please help if there is any inbuild function to be use for If-Condition just like it is there for assertion -
// await t.expect('.boarding-pass', { timeout: 10000 });
Similar option is not working under If Condition -
// if ( '.boarding-pass'.exists, { timeout: 10000 }){ do something}
It is working only if the Boarding Pass Screen is appearing, if not then it is waiting for 10 seconds and skip second If Statement.
I am not putting the codes for now. If you really need real codes to resolve my issue then I will try to recreate it using some public application.
You can use the built-in Wait Mechanism for Selectors to conditionally execute test statements.
For example, the following code waits for two elements to appear in DOM (you can specify the timeout option), and if an element exists and is visible, clicks it.
import { Selector } from 'testcafe';
fixture('Selector')
.page('http://devexpress.github.io/testcafe/example');
test ('test1',async (t)=\> {
const selector1 = Selector('#remote-testing');
const selector2 = Selector('#remote-testing-wrong', { timeout: 10000 } ); //wrong id, it will never appear
const element1 = await selector1();
const element2 = await selector2();
if(element1 && element1.visible) {
await t.click(selector1);
}
if(element2 && element2.visible) {
await t.click(selector2);
}
});
Please note, that you should use this approach only if the page contains elements that appear occasionally and unpredictably (like a chat window from a third-party component), and you can't turn them off in a test. If an element always appears on executing the test scenario, the recommended way is to use built-in wait mechanisms for actions like click, typetext, etc
UPDATE
Solved, sort of. The problem is the single-threaded nature of Javascript and my misunderstanding of its event model. The loop doesn't get interrupted by the timeout.
So, the solution is that instead of using a callback, I just grab a timestamp before starting the loop, and then each time through the loop compare against it. If more than X seconds have passed, I fire the confirm dialog:
let start = new Date();
...
while ( keepGoing && matchNotFound )
{
...
let end = new Date();
if ( end - start > 10000 )
{
keepGoing = confirm( "This run seems to be taking a while - keep going?" );
start = end;
}
}
Probably not the most efficient or idiomatic solution, but it works for my purposes.
ORIGINAL
I'm trying to call a function through setTimeout, but the callback never fires no matter what time interval I use. I don't know if the problem is in how I've set up the call, or if my understanding of how to use it is completely wrong.
I'm in the early stages of teaching myself Javascript, and have written a version of Dawkins' Weasel program as an exercise. Depending on the parameters the algorithm can take a long time to run, so my thought was to use setTimeout to call a function asking the user if they want to continue and give them the option to bail, sort of like so:
var keepGoing = true; // flag to continue main loop
var timeoutId; // stores result of setTimeout
...
/**
* Asks the user if they want to continue. If they do, reschedule this function to
* execute again after 5 seconds.
*/
function alertFunc()
{
keepGoing = confirm( "This is taking a while - do you want to continue?" );
if ( keepGoing )
{
timeoutId = setTimeout( alertFunc, 5000 );
}
}
/**
* Simulation loop
*/
function doWeasel()
{
/** simulation setup */
timeoutId = setTimeout( alertFunc, 5000 );
/** Loop until we find a match or the user gets bored */
while ( keepGoing && matchNotFound )
{
/** do the thing */
}
clearTimeout( timeoutId );
/** some cleanup */
}
That's the basic idea, anyway. The doWeasel function is tied to a button click in a form in the HTML document:
<html>
<head>
<!-- header stuff -->
</head>
<body>
<!-- long-winded explanation -->
<div class="formarea">
<form class="inputForm">
<!-- form stuff -->
<input type="button" value="OK" onclick="doWeasel()"/>
<input type="reset"/>
</form>
</div>
<div id="outputarea">
<h2>Output from the thing</h2>
<script type="text/javascript" src="weasel.js"></script> <!-- source for doWeasel() -->
</div>
</body>
</html>
The problem I'm having is that alertFunc never appears to fire - I've added console logging to write a message when the function is entered, and it never appears. I've run through the debugger in Chrome, with a breakpoint set in the callback, and it's never reached.
Obviously I'm doing something wrong either with the setTimeout call itself or how I'm trying to use it, but based on the documentation I've read so far I don't see what it could be. Is my concept of how to use it just plain wrong?
I'm an old C and C++ programmer who's only dabbled very lightly in HTML and scripting for the Web, so it's possible I just don't understand the right way to do things here.
EDIT
To address some of the comments, this is a working page - when I click OK the simulation runs and dynamically builds a table for output. It’s just that there are combinations of input parameters that cause it to run for an excessively long time (until the browser throws up a "this page is unresponsive" or I just close the tab).
I can trace the execution of the script in the debugger in Chrome, and it calls setTimeout and assigns a value to timerId, but the callback never actually executes. For the default parameters, the simulation runs to completion and builds a table on the page with the results.
Maybe this isn’t the right way to address the issue - maybe I need to add another button that calls a different function setting keepGoing to false.
EDIT2
I wrote up a quick and dirty prototype to just test the timeout functionality, and I think I know what my problem is. If I'm not in the middle of a loop, the callback executes as expected, but if I have a loop spinning it won't. So, I need to do some reading up on how to deal with asynchronous event handling.
If you're sure the while loop isn't preventing the next line from running, it's possible the script hasn't loaded yet and the function hasn't been defined by the time you assign the onclick attribute to your <input> element, try setting the defer tag to your script element to ensure the script is executed after the document has been parsed. Calling the function externally works fine otherwise.
I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.
I am porting an old game from C to Javascript. I have run into an issue with display code where I would like to have the main game code call display methods without having to worry about how those status messages are displayed.
In the original code, if the message is too long, the program just waits for the player to toggle through the messages with the spacebar and then continues. This doesn't work in javascript, because while I wait for an event, all of the other program code continues. I had thought to use a callback so that further code can execute when the player hits the designated key, but I can't see how that will be viable with a lot of calls to display.update(msg) scattered throughout the code.
Can I architect things differently so the event-based, asynchronous model works, or is there some other solution that would allow me to implement a more traditional event loop?
Am I making sense?
Example:
// this is what the original code does, but obviously doesn't work in Javascript
display = {
update : function(msg) {
// if msg is too long
// wait for user input
// ok, we've got input, continue
}
};
// this is more javascript-y...
display = {
update : function(msg, when_finished) {
// show part of the message
$(document).addEvent('keydown', function(e) {
// display the rest of the message
when_finished();
});
}
};
// but makes for amazingly nasty game code
do_something(param, function() {
// in case do_something calls display I have to
// provide a callback for everything afterwards
// this happens next, but what if do_the_next_thing needs to call display?
// I have to wait again
do_the_next_thing(param, function() {
// now I have to do this again, ad infinitum
}
}
The short answer is "no."
The longer answer is that, with "web workers" (part of HTML5), you may be able to do it, because it allows you to put the game logic on a separate thread, and use messaging to push keys from the user input into the game thread. However, you'd then need to use messaging the other way, too, to be able to actually display the output, which probably won't perform all that well.
Have a flag that you are waiting for user input.
var isWaiting = false;
and then check the value of that flag in do_something (obviously set it where necessary as well :) ).
if (isWaiting) return;
You might want to implement this higher up the call stack (what calls do_something()?), but this is the approach you need.
recently I've encountered a problem with IE. I have a function
function() {
ShowProgress();
DoSomeWork();
HideProgress();
}
where ShowProgress and HideProgress just manipulate the 'display' CSS style using jQuery's css() method.
In FF everything is OK, and at the same time I change the display property to block, progress-bar appears. But not in IE. In IE the style is applied, once I leave the function. Which means it's never shown, because at the end of the function I simply hide it. (if I remove the HideProgress line, the progress-bar appears right after finishing executing the function (more precisely, immediately when the calling functions ends - and so there's nothing else going on in IE)).
Has anybody encountered this behavior? Is there a way to get IE to apply the style immediately?
I've prepared a solution but it would take me some time to implement it. My DoSomeWork() method is doing some AJAX calls, and these are right now synchronous. I assume that making them asynchronous will kind of solve the problem, but I have to redesign the code a bit, so finding a solution just for applying the style immediately would much simplier.
Thanks rezna
Synchronous requests block the entire UI, which is horrible. You are right in your assumption, but if you want to continue down this path to the inner circles of $hell, try this:
function () {
ShowProgress();
window.setTimeout(function () {
DoSomeWork();
HideProgress();
}, 0);
}
Note that I have not tested this. Try playing with the time-out value (currently 0) if you still do not see anything.
Try throwing the HideProgress method into a setTimeout. For example:
function() {
ShowProgress();
DoSomeWork();
setTimeout(HideProgress,0);
}
Even though the delay is supposedly 0 milliseconds, this will have the net effect of throwing the HideProcess method to the end of the queue and may give the browser the breathing room it needs.
[Edit] I should have mentioned that this method does have a drawback if you invoke this method very often and rapidly: a race condition. You could end up with a previous timeout executing while another DoSomeWork() is executing. This will happen if DoSomeWork take a very long time to finish. If this is a risk, you may want to implement a counter for your progress bar and only execute HideProgress if the counter that started it is the same as the counter's present value.