I have to load two apis. The YouTube api which calls onYouTubePlayerReady when it has loaded and another api for SoundManager which calls soundManager.onready(...) when it has loaded. I do a lot of stuff in each of these ready functions to prepare the site. However, I also need to know when both have completed so I can do more initialization that require both to be fully loaded. Any idea how to have a function that is called when both of these ready functions are called?
Use a callback for both of them.
var callback = (function(){
var count = 0;
return function(){
count++;
if(count === 2){
//both ran and do something
}
}
})();
Then in the end of both onloads just do:
callback();
The things in that if statement will only run on the second time this function i s called.
Small fiddle demo: http://jsfiddle.net/maniator/2X8rF/
Just set a couple flags:
var aDone = false;
var bDone = false;
function whenADone(){
// do your other stuff
aDone = true;
if(bDone) whenBothDone();
}
function whenBDone(){
// do your other stuff
bDone = true;
if(aDone) whenBothDone();
}
There is probably a better way with Defered's, but this is simple and should work. Just keep track of what loaded and what didn't.
var status = {
youtube: false,
sound: false
};
var loaded = function() {
if (!status.youtube) return;
if (!status.sound) return;
// load stuff!
};
var onYoutubePlayerReady = function() {
status.youtube = true;
loaded();
};
soundManager.onready = function() {
status.sound = true;
loaded();
}
Using jQuery deferred, you could build promises for each ready function and combine them with jQuery.when to trigger a final action. For example
function promiseYoutube() {
var dfd = $.Deferred();
window.onYoutubePlayerReady = function() {
console.log("Youtube");
dfd.resolve();
};
return dfd.promise();
}
function promiseSoundManager() {
var dfd = $.Deferred();
window.soundManager.onready = function() {
console.log("SoundManager");
dfd.resolve();
};
return dfd.promise();
}
$.when( promiseYoutube(), promiseSoundManager() ).then(function(){
console.log('Youtube+SoundManager');
});
And a Fiddle simulating these callbacks http://jsfiddle.net/nikoshr/hCznB/
You could have both of your readys set a boolean to true then test against those a $(document).ready(function () { if (youTube === true && soundManager === true) { // Do stuff }});
If using jQuery, take a look at deferred:
http://www.erichynds.com/jquery/using-deferreds-in-jquery/
http://api.jquery.com/category/deferred-object/
Thanks
Related
I have this function. The purpose is to wait for an outside status to end before calling another function.
var renderEditClickWrapper = function( event )
{
var wait = false;
function waitForSavingDone(){
if (options.dataStatusHandler.getStatus() == 'saving'){
wait = setInterval( function(){
waitForSavingDone();
}, 800);
}else{
wait = false;
call.renderEdit(event.data.name, event.data.rowId, event.data.parentId, event.data.options );
}
}
if (!wait) waitForSavingDone();
return false;
};
This works, however when the functions waits once, the function is called over and over.
I'm using jQuery as well.
Any idea what I am doing wrong?
Here is one more alternative you may try:
Define a wait function as below:
function wait(waitComplete, onWaitComplete){
if (waitComplete()) {
onWaitComplete();
return true;
}
setTimeout(function(){
console.log('waiting...');
wait(waitComplete, onWaitComplete);
}, 800);
return false;
}
Event handler can use wait as below:
var renderEditClickWrapper = function( event )
{
function isWaitComplete() {
return (options.dataStatusHandler.getStatus() != 'saving');
}
function onWaitComplete() {
call.renderEdit(event.data.name, event.data.rowId,
event.data.parentId, event.data.options);
}
wait(isWaitComplete, onWaitComplete);
};
Try using clearInterval() method to cancel the interval function, instead of wait = false. And also you misunderstood the usage of interval functions. Do it like below:
var renderEditClickWrapper = function( event )
{
function waitForSavingDone() {
if (options.dataStatusHandler.getStatus() !== 'saving') {
clearInterval(wait);
}
}
var wait = setInterval(waitForSavingDone, 800);
return false;
};
You should check the callback function as mentioned in the comments. It'll be more recommended if you use callback instead.
Due to certain reason, we are going to remove jquery from our legacy app (Please don't ask why!)
However, there 1000+ of template files by designers, are making used of jquery ready function. We plan to make the following mock strategy.
<head>
<script type="text/javascript">
// // http://stackoverflow.com/a/9899701
(function(funcName, baseObj) {
// The public function name defaults to window.docReady
// but you can pass in your own object and own function name and those will be used
// if you want to put them in a different namespace
funcName = funcName || "docReady";
baseObj = baseObj || window;
var readyList = [];
var readyFired = false;
var readyEventHandlersInstalled = false;
// call this when the document is ready
// this function protects itself against being called more than once
function ready() {
if (!readyFired) {
// this must be set to true before we start calling callbacks
readyFired = true;
for (var i = 0; i < readyList.length; i++) {
// if a callback here happens to add new ready handlers,
// the docReady() function will see that it already fired
// and will schedule the callback to run right after
// this event loop finishes so all handlers will still execute
// in order and no new ones will be added to the readyList
// while we are processing the list
readyList[i].fn.call(window, readyList[i].ctx);
}
// allow any closures held by these functions to free
readyList = [];
}
}
function readyStateChange() {
if (document.readyState === "complete") {
ready();
}
}
// This is the one public interface
// docReady(fn, context);
// the context argument is optional - if present, it will be passed
// as an argument to the callback
baseObj[funcName] = function(callback, context) {
// if ready has already fired, then just schedule the callback
// to fire asynchronously, but right away
if (readyFired) {
setTimeout(function() {
callback(context);
}, 1);
return;
} else {
// add the function and context to the list
readyList.push({
fn: callback,
ctx: context
});
}
// if document already ready to go, schedule the ready function to run
if (document.readyState === "complete") {
setTimeout(ready, 1);
} else if (!readyEventHandlersInstalled) {
// otherwise if we don't have event handlers installed, install them
if (document.addEventListener) {
// first choice is DOMContentLoaded event
document.addEventListener("DOMContentLoaded", ready, false);
// backup is window load event
window.addEventListener("load", ready, false);
} else {
// must be IE
document.attachEvent("onreadystatechange", readyStateChange);
window.attachEvent("onload", ready);
}
readyEventHandlersInstalled = true;
}
}
})("docReady", window);
// Mock jquery.
var jQuery = function(baseObj) {
return {
ready: function(baseObj) {
docReady(baseObj);
}
}
};
var $ = jQuery;
</script>
</head>
Take note, we tend to mock jquery ready with the following code snippet.
// Mock jquery.
var jQuery = function (baseObj) {
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
var $ = jQuery;
It works for cases
jQuery(document).ready(function() {...});
$(document).ready(function() {...});
However, how can we make the following syntax works as well?
$(function() {...});
Check if the passed parameter is function
var jQuery = function(baseObj) {
if (typeof baseObj === 'function')
return docReady(baseObj);
Code:
// Mock jquery.
var jQuery = function (baseObj) {
if (typeof baseObj === 'function')
return docReady(baseObj);
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
var $ = jQuery;
In the third case you are actually not calling the ready function. As it is just bind a function in it .
$(function() {...});
So the below function is not called , you are just return an object function which is not called.
var jQuery = function (baseObj) {
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
You need to called the function directly for the third case, as below.
// Mock jquery.
var jQuery = function (baseObj) {
if (typeof(baseObj) === 'function') docReady(baseObj);
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
Demo : http://jsfiddle.net/kishoresahas/vnd92c1u/
I also had alike situation, a some what similar code is live for long now.
I am really having trouble wrapping my head around the deferred() method inside jquery. I've spent several hours reading the documentation, but I still don't fully understand what I'm doing.
My basic problem is, I have a series of functions (not ajax calls) that I want to run in sequence, but stop all processes if there is an error in any of them.
Here is how far I've gotten (I've stripped out a bunch of unneeded code and just left the basic idea)
//The module var myModule = (function() {
//Defaults
var vOne;
var VTwo;
var deferred = $.Deferred();
//Private method
var _myPrivateFunction1 = function(userID) {
if(userID >= 10) {
//All is good, set var vOne to true and run next function
vOne = true;
return deferred.promise();
} else {
//User is not allowed, stop everything else and show the error message
return deferred.reject();
}
}
var _myPrivateFunction2 = function() {
if(vOne === true) {
//Ok we can keep going
return deferred.promise();
} else {
//again stop everything and throw error
return deferred.reject();
}
};
var _myPrivateFunction3 = function(element) {
//...and so on
};
var _errorMsgFunction = function(msg) {
$.log("There was an error: " + msg);
return false;
};
//Public method
var myPublicFunction = function(element,call) {
//element is jquery object passed through user "click" event
var userID = element.data('id')
var userAction = element.data('action');
//Now... i want to fire _myPrivateFunction1, _myPrivateFunction2, _myPrivateFunction3 in sequence and STOP all processes, and run
// _errorMsgFunction if there is an error in any of them.
//This is how far I've gotten...
_myPrivateFunction1(userID).then(_myPrivateFunction2(userAction), _errorMsgFunction("Error in _myPrivateFunction2")).then(_myPrivateFunction3(element),_errorMsgFunction("Error in _myPrivateFunction3")).fail(_errorMsgFunction("Error in _myPrivateFunction1"));
};
// Public API
return {
myPublicFunction: myPublicFunction
};
})();
So right now I keep getting "Error in _myPrivateFunction2" (I'm forcing this error for testing purposes), but the other functions after continue to fire...They don't stop. What am I missing here?
You cannot share deferred objects. You should create a different promise from a deferred for each function.
Here is some very simple example, using sycnhronus functions for the sake of simplicity, although promises are meant to be used with asynchronous functions:
var func1 = function(arg){
var dfd = jQuery.Deferred();
if (arg === 0) {
dfd.resolve('func1 Ok');
} else {
dfd.reject('func1 arg != 0');
}
return dfd.promise();
}
var func2 = function(arg){
var dfd = jQuery.Deferred();
if (arg === 0) {
dfd.resolve('func2 Ok');
} else {
dfd.reject('func2 arg != 0');
}
return dfd.promise();
}
var func3 = function(arg){
var dfd = jQuery.Deferred();
if (arg === 0) {
dfd.resolve('func3 Ok');
} else {
dfd.reject('func3 arg != 0');
}
return dfd.promise();
}
If the functions does not depend on other to do their processing, we can do it in parallel using jQuery.when
// Parallel processing
jQuery.when(func1(1), func2(0), func3(0)).then(function(res1, res2, res3){
console.log(res1, res2, res3);
}).fail(function(reason){
console.error(reason); // will fail with reason func1 arg != 0
});
If it is a sequence processing (as I undertood your problem is), you should do:
// Sequential processing
func1(0).then(function(res1){
return func2(res1);
}).then(function(res2){
return func3(res2);
}).then(function(res3){
// everything ran ok, so do what you have to do...
}).fail(function(reason){
console.log(reason);
});
The code above will fail with reason:
> func2 arg != 0
If you have mixed parallel and sequential processing to do, then you should mix both approaches.
Disclaimer
As in my example, if func1 or func2 have side effects, they will not be undone within fail() by themselves.
The best practice is to only have side effects when you are absolutely sure that everything went ok, that is, inside the last then() call.
You will need a separate $.deferred() inside each of your functions, because you want to return unique promise for each function.
//Private method
var _myPrivateFunction1 = function(userID) {
var deferred = $.Deferred();
if(userID >= 10) {
//All is good, set var vOne to true and run next function
vOne = true;
deferred.resolve();
} else {
//User is not allowed, stop everything else and show the error message
deferred.reject();
}
return deferred.promise();
}
Then your public function should work.
So i'm using angularJS and$q service. But for simplicity i'm using $timeout since it creates a promise.
Question:
Is it possible to only return a promise when a conditional has been satisfied? For this example, I want to wait for carousel.params.caruselReady to return true before I move to next .then.
$timeout(function(){
if(Carousel.params.ready()){
return ready;
}
},0).then(function(ready){
//...do stuff..//
}
Carousel.params.ready is coming from Carousel.js a factory:
//this function gets called everytime a image has been loaded. when all images are rendered than carousel is ready
carouselElLoaded: function (result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
if (Carousel.params.pageRenderedLength >= Carousel.params.pageLength) {
Carousel.params.carouselReady = true;
}
},
Lastly, carouselElLoad is being called from wa.pages.js (a directive)
$img.onload = function (e) {
var self = this;
$timeout(function () {
return self;
}, 0).then(function () {
Carousel.set.carouselElLoaded(e);
});
};
Currently, I'm using a $watch to achieve this but I was wondering if I could accomplish the same w/o a watcher.
You can use a promise instead of a boolean flag and it will do exactly what you want.
In Carousel.js define a promise names isCarouselReady and resolve it once the carousel is ready, your code should like something like this:
// During the initialisation of your factory
var carouselDeferred = $q.defer()
Carousel.params.isCarouselReady = carouselDeferred.promise;
//this function gets called everytime a image has been loaded. when all images are rendered than carousel is ready
carouselElLoaded: function (result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
if (Carousel.params.pageRenderedLength >= Carousel.params.pageLength) {
carouselDeferred.resolve(/* What ever you'd like here */);
}
},
Now you all you have to do in order to use it is:
Carousel.params.isCarouselReady.then(function() {
// Your logic
});
Your last part would be nicer if it would look something like this:
$img.onload = function (e) {
$scope.$apply(function(){
Carousel.set.carouselElLoaded(e);
});
};
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 .