I have a function that performs a long task. I would like to create a function that is able to notify the caller of the progress. Ultimately I want to update the UI with the current progress.
Something like this:
function myLongMethod(progressCallback)
{
for(var i = 0 ... )
{
progressCallback(i) ;
}
}
This works but updates on UI are not smooth. Is there a better way? I would prefer something with a jquery Deferred object using deferred.notify(). Any ideas?
Your code is fine. You have got another problem. Javscript always runs on the UI thread. Your operation is blocking this thread (the browser) and you will see some blocking of your browser window.
Luckily there is a workaround implemented in modern browser called web workers. It's simple just call in your main script another script which then get executed:
var w = new Worker("another_script.js");
If your worker is ready you can react on the result by adding a event listner to the worker:
w.onmessage = function(event) {
//do something
}
When you use this pattern, your UI did not block. You can even return data from a web worker and include scripts into it. More details you can find here and here is a good starting tutorial.
Hi you can apply the easing effect to your UI for smoothness and i am giving the following code it may help you
var oldProgress = 0;
var uiUpdater = null;
function updateUI(newProgress){
if(uiUpdater !=null){
// update your ui to the old progress first
window.clearInterval(uiUpdater); // clearing the previous timer
}
var diff = newProgress - oldProgress;
oldProgress = newProgress;
var stepSize = diff/5; // applying the new change in 5 steps to the UI
uiUpdater = window.setInterVal(function(){
// updating your UI after every 100 milliseconds
// to give the smoothness
diff -= stepSize; // decreasing the difference gradually
if(diff<=0){
window.clearInterval(uiUpdater); // clearing the interval once the update is done
}
},100);
}
You have to call the "updateUI" method from you callback with the new progress.
Related
I am making a general loading animation for my angular application.
I have a service which toggles a loading animation on and off and the events are happening as I expect them to, my issue is that the UI is not updating with the events.
The turn on and turn off of the loading animation occurs in the same function call so my guess is that the ui isn't updated until the function call completes which basically means that my turning on and off or the loading animation has no effect.
How can I write this in a way that the UI will be updated as the variables change. I added in a pause to simulate heavy calculation on load just to check... which is the datetime stuff.
The two broadcasts are the start and stop of the loading state.
function activateController(promises, controllerId) {
var startData = { controllerId: controllerId };
$broadcast(configcommonProvider.config.controllerActivateStartEvent, startData);
return $q.all(promises).then(function (eventArgs) {
var e = new Date().getTime() + (2 * 1000);
while (new Date().getTime() <= e) { }
var successData = { controllerId: controllerId };
$broadcast(configcommonProvider.config.controllerActivateSuccessEvent, successData);
});
}
Try calling $apply() on your scope object after the broadcast, it should force a digest cycle which should update the UI.
JavaScript is single-threaded and - conceptually - the changes in the GUI are on the same thread as the program.
This is exactly why there are so many callbacks in Javascript APIs (which you might have noticed working with AJAX). The only way to get around the single-threaded nature of JS is not to wait for something to finish at all. Instead - to be called back when something is finished.
What you want to do (long running calculations) is something very unnatural in JS, so the only real solution will be quite verbose. You need to run a webworker in parallel to your code and wait until it signals that it is finished. The shortest no-nosense example of using webworkers is probably the official HTML5 specification on WHATWG site.
So my website is experiencing lag when scrolling. I just wanted to ask if it's a good practice to initialize the jQuery objects that you need in $(window).scroll(function () {}?
For example:
$(window).scroll(function () {
$thisContainer = $('.section #container');
// 10 more initializations...
$thisContainer.css(); // something like that...
}
I feel like it wouldn't be a good idea since this function gets called really often every time the user scrolls. And when it is called, those variables would be reinitialized. That as a result would waste a whole lot of memory and time.
Thanks!
In general you should avoid doing anything inside a callback that was fired by the scroll event because the callback will be executed for every pixel that the window is scrolled. However, depending on the application that you're building, some things simply cannot be avoided inside that callback.
Doing a lot of "expensive" manipulations or queries inside the scroll callback can totally freeze the browser and will make your application unusable, so you have to be very careful and performance cautious.
Here are some examples of good practices.
A general example:
Live example: http://jsfiddle.net/tfjyf0a3/
// Since we can get away with querying the elements outside of the callback
// our application will be a lot snappier because we're doing less work for
// every scrolled pixel. Always query DOM elements outside if possible.
var $output = $('#output');
var $window = $(window);
// This function is executed for every scrolled pixel, so we need to
// avoid doing "expensive" queries or changing the DOM in here too.
function changeFontSize(scrollNumber) {
// Setting the fontSize here is unavoidable because our application needs it.
$output.css('fontSize', scrollNumber <= 50 ? 18 : Math.floor(scrollNumber/10));
}
$window.on('scroll', function() {
// Since `$(this)` here would be the window object, it's better to
// just use the previous reference named `$window`.
// Querying the scrolled position here is unavoidable because our
// application needs it.
var currentScroll = $window.scrollTop();
// Saving a reference of the `scrollTop()` value is better when
// we need to re-use its value.
$output.html(currentScroll + 'px');
// We have to be cautious inside this function as well.
changeFontSize(currentScroll);
});
// This is a good practice when you need to guarantee the execution of the function
// when there isn't enough content in the body to cause a scrollbar in the Browser.
//
// The 'scroll' event will fire only when there is a scrollbar in the Browser.
$window.scroll();
Sometimes you will need to do "expensive" DOM manipulations, queries, or even Ajax requests inside the scroll's callback function. For example imagine building an application that implements a pattern known as infinite loading. In this application when the user has reached close to the bottom of the page by scrolling quickly or slowly, you will need to do the following:
Check if the user has scrolled to the bottom.
Check if there are more resources to load.
Load the resources.
Append the new resources to the DOM.
You definitely wouldn't want to execute all the steps above on every scrolled pixel. A very good practice for this situation is to delay the steps above. An example might look like this:
Delayed execution:
Live example: http://jsfiddle.net/35qb1b88/
var $output = $('#output');
var $window = $(window);
var timer;
function changeFontSize(scrollNumber) {
$output.css('fontSize', scrollNumber <= 50 ? 18 : Math.floor(scrollNumber/10));
// ...
// load resources
// append in DOM
// ...etc
}
function scrollHandler() {
var currentScroll = $window.scrollTop();
$output.html(currentScroll + 'px');
changeFontSize(currentScroll);
}
// This callback will be executed for every pixel but it will
// do nothing if we're scrolling too fast because we're clearing
// the timeout. Only when scrolling has stopped and at least 100
// milliseconds have passed will the `scrollHandler` function run.
$window.on('scroll', function() {
timer && window.clearTimeout(timer);
timer = window.setTimeout(scrollHandler, 100);
});
$window.scroll();
The same principles would apply for the resize event too.
I am developing a web application in node.js to collect data from devices on a network using snmp. This is my first real encounter with node.js and javascript. In the app each device will be manipulated through a module I named SnmpMonitor.js. This module will maintain basic device data as well as the snmp and database connection.
One of the features of the app is the ability to constantly monitor data from smart metering devices. To do this I created the following code to start and stop the monitoring of the device. It uses setInterval to constantly send a snmp get request to the device. Then the event listener picks it up and will add the collected data to a database. Right now the listener just prints to show it was successful.
var dataOIDs = ["1.3.6.1.2.1.1.1.0","1.3.6.1.2.1.1.2.0"];
var intervalDuration = 500;
var monitorIntervalID;
var dataCollectionEvent = "dataCollectionComplete";
var emitter = events.EventEmitter(); // Uses native Event Module
//...
function startMonitor(){
if(monitorIntervalID !== undefined){
console.log("Device monitor has already started");
} else {
monitorIntervalID = setInterval(getSnmp,intervalDuration,dataOIDs,dataCollectionEvent);
emitter.on(dataCollectionEvent,dataCallback);
}
}
function dataCallback(recievedData){
// receivedData is returned from getSnmp completion event
// TODO put data in database
console.log("Event happened");
}
function stopMonitor(){
if(monitorIntervalID !== undefined){
clearInterval(monitorIntervalID);
emitter.removeListener(dataCollectionEvent,dataCallback);
} else {
console.log("Must start collecting data before it can be stopped");
}
}
//...
I also have a test file, test.js, that requires the module, starts monitoring, waits 10 seconds, then stops it.
var test = require("./SnmpMonitor");
test.startMonitor();
setTimeout(test.stopMonitor,10000);
My problem is that the setInterval function in startMonitor() is not being run. I have tried placing console.log("test"); before, inside, and after it to test it. The inside test output never executes. The monitorIntervalID variable is also returned as undefined. I have tested setInterval(function(){ console.log("test"); },500); in my test.js file and it runs fine with no issues. I feel like this is a noobie mistake but I just can't seem to figure out why it won't execute.
Here is a link to the entire module: SnmpMonitor.js
I not sure exactly what was wrong but I got it to work by overhauling the whole class/module. I thought the way I had it was going to allow me to create new monitors objects but I was wrong. Instead I created two functions inside the monitor file that do the same thing. I changed the start function to the following.
SnmpMonitor.prototype.start = function() {
var snmpSession = new SNMP(this.deviceInfo.ipaddress,this.emitter);
var oids = this.deviceInfo.oids;
var emit = this.emitter;
var duration = this.intervalDuration;
this.intervalID = setInterval(function(){
snmpSession.get(dataCollectionEvent,emit,oids);
},duration);
};
The setInterval function seems to work best when the callback function is set inside an anonymous function, even though technically you can pass it directly. Using the this. notation I created some class/module/function variables (whatever its called in js) that are in scope of the whole class. For some reason the variables accessed through this. do not work so well when directly in a function or expression so I created temp variables for them. In my other version all the variables were global and js doesn't seem to like that.
I want to use a timer as a fallback in case I end up in an infinite loop. It seems that set interval is the right way to do this. However, it's not working for me.
From my research, it seems like setInterval should run in a separate thread in the background, but I don't see it.
Why is this behavior happening? And how do I solve this?
var time = 0;
window.setInterval(function(){time++;}, 1000);
while (true) {
//stuff done
if (time >= 5) {
break;
}
}
Browser javascript runs in a single thread. So if you perform something that takes too long - it will freeze browser.
See John Resig article for further details: http://ejohn.org/blog/how-javascript-timers-work/
After you read that article you'll get that your setInterval callback queued to be run in 1000ms after now but only after the current code is finished. It cannot finish though, because of the infinite loop.
zerkms has the correct answer. But I would add that web workers are a way to get some multi-threaded-ish behavior from client side javascript.
var worker = new Worker('my_task.js');
worker.onmessage = function(event) {
console.log("Called back by the worker!\n");
};
The worker runs in a background thread, and you can exchange messages and subscribe to events. It's pretty nifty.
As has been already said - the callback to setInterval doesn't run until the infinite loop finishes. To do what you are trying to achieve - without using web workers - you have to check the time from the loop itself:
var start = Date.now();
while((Date.now() - start) < 5000){
...
}
I'm trying to get the vertical position of the browser scroll bar with javascript. I am currently using the jquery scroll() function, but I feel like the callbacks are a bit slow, and I'm wondering if there is a faster, native way to use a callback when a user scrolls in the browser. Does anyone know?
If you feel the callback is taking too much time to run, you could throttle it. Just keep track of the time it was last called: http://jsfiddle.net/fjjAw/.
var lastCalled = 0;
$(window).on("scroll", function() {
var now = Date.now();
if(now - lastCalled > 1000) { // only proceed if it has been one second since last call
lastCalled = now;
// do things
}
});
Here is the browser DOM level event to listen to. I recommend using jQuery since it will use this same native event and takes the pain out of crass platform / cross browser issues that can sometimes arise.
window.addEventListener ("scroll", function(){console.log('scrolling');});