Creating an async method in javascript - javascript

I'm writing javascript code to update the interface from the user.
This task has to be done in the background and shouldn't block anything.
The code is currently blocking/crashing the browser, the while loop is causing the issue.
What i would like is to wait till the checking and installing are complete and then perform other actions also in between. I want to avoid to write the scenario: TimeOut in a TimeOut in a TimeOut, which does work but it makes the code a mess.
updateChecking();
function updateChecking() {
setTimeout(function() {
if (settings.IsChecking === "True") {
var isChecking = false;
var isInstalling = false;
// PROBLEM, Wait till checking is completed
while (isChecking) {
var timerUpdateChecker = setInterval(function() {
getIsCheckingUpdates().done(function(resultIsChecking) {
if (resultIsChecking === "False") {
isChecking = true;
clearInterval(timerUpdateChecker);
}
});
}, 1000);
checkForSystemUpdates();
startCheckingDownloads();
// PROBLEM, Wait till installing is completed
while (isInstalling) {
setInstallerInterface();
var timerInstallChecker = setInterval(function() {
getIsInstallingUpdates().done(function(resultIsUpdating) {
if (resultIsUpdating === "False") {
isInstalling = true;
clearInterval(timerInstallChecker);
}
});
}, 1000);
}
viewUpdateInstalledAlert();
getAvailableUpdates();
unsetInstallerInterface();
};
}
}, 0);
}
Any suggestions that could solve my issue?

While loops run until the condition if false. Now you're setting the variable to false and call while. So I'm not sure how the while will work. From what I understand it should be while(!isChecking).
That said, you should maybe try to replace your setTimeout and setInterval and replace with events. You can create custom event, dispatch and listen. I think it would be much more efficient. Something like this:
var event = new Event('isInstalled');
In your installed function you dispatch the event:
window.dispatchEvent(event);
And you listen to the event to trigger wanted functions.
window.addEventListener('isInstalled', function(){
//whatever needs to happen when installed is done
})

Related

Javascript SetTimeout Function Not Pausing After Execution

I am checking for a change in a CSS class. I want the check to happen every 1000 milliseconds. Currently in the Google Chrome console, I can see it checking without any delay, causing the webpage to crash. What is the issue with the below setTimeout function? Perhaps there is another way of checking for a change in a CSS class also?
var itemInfo = null;
itemInfo = document.getElementById('option-item-info');
while (itemInfo.className == "disabled") {
console.log("Test 1");
getOptionItemInfo();
}
function getOptionItemInfo() {
setTimeout(function() {
console.log("Checking...");
itemInfo = document.getElementById('option-item-info');
}, 1000);
}
Your while loop isn't going to wait for the setTimeout async callback, it's just going to trigger tons of setTimeouts until your browser crashes. Instead, just kick off the setTimeout once and then only call it again from within the callback if you haven't met the desired condition.
getOptionItemInfo();
function getOptionItemInfo() {
setTimeout(function() {
console.log("Checking...");
var itemInfo = document.getElementById('option-item-info');
if (itemInfo.className == "disabled") {
getOptionItemInfo();
} else {
console.log("done!");
}
}, 1000);
}

For Loop in a Page Object Not Running Properly

I am trying to check for the presence of a modal. If the modal is not present then it will place the value of the timer into browser.sleep(). This will give time for the modal to appear. I am having an issue with a for loop in a page object. When I run the code below I do not receive the alert and console.log messages under the if when I force a failure by getting changing the object. Also, I do not receive the Timer expired message.
from page_object file (relevant code)
editVinModal: { get: function () {
return browser.element({id: 'editableVINPart'});
}},
doEditVIN: { value: function () {
modalFailedToAppear = true;
console.log('In doEditVIN');
for(modal_timer = 0 ; modal_timer <= 30; modal_timer++) {
if (!(this.editVinModal)) {
alert('In If');
console.log('Modal failed to appear');
console.log('Under if - modalFailedToAppear: ', modalFailedToAppear);
browser.sleep(modal_timer);
console.log('under if - modal_timer: ',modal_timer);
}
else {
console.log('In else if else loop');
// console.log(browser.isElementPresent(this.editVinModal));
console.log('modalFailedToAppear: ',modalFailedToAppear);
modalFailedToAppear = false;
console.log('modalFailedToAppear: ',modalFailedToAppear);
console.log('modal_timer: ',modal_timer);
break;
}
}
if (modalFailedToAppear){
console.log("Modal is not present within the given time period. Timer has expired.");
}
this.editVinLink.click();
}},
Thanks in advance for
Looks like you're new around here. Welcome!
browser.sleep(), generally speaking, does not belong in your Protractor tests (except for debugging purposes). That's the bad news. The good news is that Protractor actually provides a function that does exactly (I think) what you're trying to do. It's called browser.wait() and it works like this:
browser.wait( function() {
return element(by.id('editableVINpart')).isPresent().then( function(present) {
return present;
});
}, 5000)
.then(function() {
element(by.id('editableVINpart')).click();
}, function() {
console.log('Element not found. :( ');
});
browser.wait() takes two arguments: first, an anonymous function, which it will execute repeatedly until it returns true; second, an amount of time to wait in milliseconds (by the way, browser.sleep() also takes a millisecond wait time, so your for loop is only waiting 465 milliseconds if it iterates all the way through, or about a half second--not very long).
Then, since browser.wait() returns a promise, just like all Protractor functions, we can attach a .then() statement to the end of it, which will execute the first passed-in function if the promise is successful, or the second passed-in function if it is not.
If you often have to wait for an element to be present (and for some reason it isn't synchronized with the Angular page load), it may be useful to you to have a reusable form of the function, like this:
var waitThenClick = function(el) {
browser.wait( function() {
return el.isPresent().then( function(present) {
return present;
});
}, 5000)
.then(function() {
el.click();
}, function() {
console.log('Element with locator: ' + el.locator + ' was not found. :( ');
});
};
Then you could just call it like this, for whatever element you need:
waitThenClick(element(by.id('editableVINpart')));
Good luck! Make sure to get good and clever with asynchronous stuff (especially promises) with problems like this. Protractor promises trip up the best of us.

Pause execution in while loop locks browser (updated with fiddles)

I know my problem I just not sure how to resolve it. I have a custom domain and in a function call a while loop executes. In that loop i wanted an animation to occur in order.
So the first problem is that javascript by its nature executes every line thus item 2 starts before item 1 completes. Now the effect is so short that it "appears" to happen to all elements at once but in the debugger it is just looping one at a time.
Now my typical resolution would be to use SetTimeout() but that is causing the browser to lock. Reading this post (Trying to delay/pause/slow a while loop in jQuery) it makes sense that the browser is getting into an endless loop.
So how can I get a pause between element1 and element2 events? I thought perhaps to add a callback function to my custom domain but not sure if that will work as desired besides not being sure how to do it.
In the head of the page and read the comments for anything else I may be doing wrong or could do better.
$(document).ready(function ()
{
//pause long enough for person to visually take in page before starting
setTimeout(function () { PageLoadAnimation.onReady(); }, 1000);
});
My custom domain:
var PageLoadAnimation =
{
onReady: function ()
{
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
PageLoadAnimation.FlashElement();
},
BlackOutElements: function ()
{
$('#ParentContainer').children().hide();
},
FlashElement: function ()
{
//get array of all elements and loop till all are visible
var elementArray = $('#ParentContainer').children();
var $els = $('#PartialsContainer').children();
while (elementArray.length)
{
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
//if I put set timeout here is causes the infinite loop
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
//if I put by itself it no diff as the while loop continues regardless
//setTimeout(1500);
}
},
FlashBlast: function ($el)
{
//flash background
$el.fadeIn(200, function () { $el.fadeOut(200) });
}
}
I'm not sure if it isn't working or if I am doing something wrong so I created these fiddles:
Original Fiddle
With Johan Callbacks
Using is animating property
WARNING THIS ONE WILL HANG YOUR BROWSER!
I don't think I am checking the isAnimating property the way Johan had in mind??
ANSWER FOR THIS SITUATION. Hopefully it will help others.
setTimeout in a loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code I read here (http://javascript.info/tutorial/settimeout-setinterval) setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
A callback example that might help:
FlashBlast: function ($el, fadeInComplete, fadeOutComplete)
{
if(arguments.length === 3){
$el.fadeIn(200, function () {
fadeInComplete();
$el.fadeOut(200, fadeOutComplete);
});
}
}
Usage:
PageLoadAnimation.FlashBlast($el, function(){
//fadein complete
}, function(){
//fadeout complete
});
Another idea that might help:
isAnimating: false,
FlashBlast: function ($el)
{
var dfd = $.Deferred(),
that = this;
that.isAnimating = true;
$el.fadeIn(200, function () {
$el.fadeOut(200, function(){
dfd.resolve();
})
});
dfd.done(function(){
that.isAnimating = false;
});
}
Then make use of the private property isAnimating.
Finally, to know if an element is under an animation, you can use $el.is(':animated').
Hope this helps. Let me know if anything is unclear.

running code when two events have triggered

This is mostly a language-agnostic question.
If I'm waiting for two events to complete (say, two IO events or http requests), what is the best pattern to deal with this. One thing I can think of is the following (pseudo js example).
request1.onComplete = function() {
req1Completed = true;
eventsCompleted();
}
request2.onComplete = function() {
req2Completed = true;
eventsCompleted();
}
eventsCompleted = function() {
if (!req1Completed || !req2Completed) return;
// do stuff
}
Is this the most effective pattern, or are there more elegant ways to solve this issue?
Before even going into the details, here's something neat that takes advantage of lambda functions off the top of my head:
function makeCountdownCallback(count, callback) {
return function() {
if (--count == 0)
callback();
};
}
request1.onComplete = request2.onComplete = makeCountdownCallback(2, function() {
// do stuff
});
This obviously assumes that each event fires at most once, and doesn't take advantage of order.
jQuery 1.5 has Deferreds: http://api.jquery.com/category/deferred-object/
You can easily set them up to call back only when some events have been triggered.
Try #1: Here's a solution that doesn't require additional global variables:
request1.onComplete = function() {
// register new handler for event2 here, overwriting the old one
request2.onComplete = function() {
// now they're both done
}
}
request2.onComplete = function() {
// register new handler for event1 here, overwriting the old one
request1.onComplete = function() {
// now they're both done
}
}
The handler for whichever event fires first will clear the other's old handler and assign a new one that includes the stuff you need to do after the completion of both events. Because we re-assign the second handler inside the handler of the first event (whichever that is), we always know we're done when that second handler finishes.
Try #2: Here's something that will work if each event type is different:
function onBoth(fn) {
var last, done = false;
return function(e) {
if (last && last !== e.type && !done) {
fn(); // done
done = true;
}
last = e.type;
}
}
For example, this won't alert "done" until the user has both scrolled and clicked:
var both = onBoth(function() {
alert("done")
});
document.addEventListener("scroll", both, false);
document.addEventListener("click", both, false);
Try #3: The previous try can be modified to work for similar events:
function onBoth(fn) {
var last, done = false;
return function(curr) {
if (last && last !== curr && !done) {
fn(); // done
done = true;
}
last = curr;
}
}
...which should be used like this:
var check = onBoth(function() {
alert("done")
});
request1.onComplete = function() {
check(arguments.callee);
}
request2.onComplete = function() {
check(arguments.callee);
}
Basically, this checks that two different callbacks have executed by storing a reference to the most recently executed call back. Its usage is a little clunky, but it gets the job done (i.e. it will still work if each of the events executes more than once).
One way to do it: http://tobireif.com/posts/waiting_for_two_events/
Q.spread([
getA(), getB()
], function(a, b) {
// Use the results a and b.
// Catch errors:
}).catch(function(error) {
// Minimal for this example:
console.log(error);
});
The lib is at https://github.com/kriskowal/q .

How to detect when one function is complete from another function?

I have a javascript function that is being built to animate the collapse of a div, and then proceed with other jobs. The code is as follows:
function newsFeed() {
var self = this;
this.collapse = function(listingID,orig_height,curr_height,opacity) {
var listing = document.getElementById(listingID);
var reduceBy = 5;
if(curr_height > reduceBy) {
curr_height = curr_height-reduceBy;
listing.style.overflow = "hidden";
listing.style.height = (curr_height-40) + "px";
if(opacity > 0) {
opacity = opacity - 10;
var opaque = (opacity / 100);
listing.style.opacity=opaque;
listing.style.MozOpacity=opaque;
listing.style.filter='alpha(opacity='+opacity+')';
}
setTimeout(function() { self.collapse(listingID,orig_height,curr_height,opacity); },1);
}else{
return true;
}
}
this.remove = function(listingID) {
var listing = document.getElementById(listingID);
var currHeight = listing.offsetHeight;
if (this.collapse(listingID,currHeight,currHeight,100)) {
// DO SOME OTHER STUFF
}
}
}
var newsFeed = new newsFeed();
newsFeed.remove('closeMe');
I cannot get the this.remove function to wait while this.collapse finishes and returns true. Is this impossible? What is the best way to go on?
Important: I would like to be able to use this.collapse with other functions yet to be built in the same fashion as I do here.
I cannot get the this.remove function to wait while this.collapse finishes
That is correct, it is impossible to do so. In JavaScript there is a single flow of execution. When the browser calls your code you can do some processing, but for anything further to occur (timeouts or event calls) you must return control to the browser.
‘Asynchronous’ processes like collapse() are done by setting timeouts, so control must be returned to the browser many times; when remove() calls collapse() the first time it returns immediately after the first timeout is set; that timeout cannot be fired until remove() itself returns, so your 'if' code will only ever execute if the very first call to collapse() was the last frame of animation (ie. the element was 5px or smaller already). Otherwise collapse()'s ‘return true’ will just be returning true to the browser's timeout-caller, which doesn't care at all what value you return to it.
Some languages give you tools such as threads or coroutines that can allow an asynchronous routine to be run from a synchronous routine; JavaScript does not. Instead, remove() must supply collapse() with a callback function it can call itself on the last frame.
There is no way you can pause the execution in Javascript till something else happens. All you can do is attach a callback function to collapse to call after it is done executing the final step.
As a sidenote, jQuery provides functions like fade(), animate() etc and supports queuing. If you don't want to use jQuery, you can still look at the code to see how it's implemented.
See the examples in this page.
setTimeout is not a "sleep". The function will end right there and return "undefined".
To manage that, I think you should do something like:
var newsFeed = new newsFeed();
newsFeed.onaftercollapse = function () {
newsFeed.remove('closeMe'); // "newsFeed" or "self"? must test
};
And then instead of return true;, the collapse() will end with:
if (self.onaftercollapse) self.onaftercollapse();
This example demonstrates how to check if a function is complete.
function foo() {
foo.complete = false;
// your code here
foo.complete = true;
}
foo.complete = false;
if (foo.complete) { // foo execution complete
// your code here
}
This code demonstrates how to check if a function has been run once.
function foo() {
// your code here
foo.ranOnce || (foo.ranOnce = true);
}
foo.ranOnce = false;
if (foo.ranOnce) { // foo execution complete at least once
// your code here
}

Categories

Resources