I'm really struggling with using Deferred or When with my jquery script. I've been through a number of articles here and elsewhere (including api.jquery.com) and I think I'm just a bit too beginner to understand exactly how to use these calls.
I'm trying to defer a function from running until a previous function in my script has completed.
I have
function datapoints () {
//My long function here where I define $data
};
of which the outcome is an array named $data. I need that $data variable to be defined going into my next function, so I want to defer it.
I've tried to set up the deferral like this:
var deferred = $.Deferred();
deferred.resolve(datapoints());
deferred.done(function result (){
// My function here where I use $data
}
);
I'd really appreciate some pointers on how to clean this up. I've tried recreating various examples but the result every time is a console error saying that $data is undefined. I know the code works because when I manually put in a setTimeout of a few seconds before running the second function everything works fine but I think deferred is a better solution.
Thanks in advance!
Try fixing your code like this:
deferred.done(function result (data){
// Do not use global $data, use local 'data' instead
}
);
This will ensure the data you are using is in fact the data returned by datapoints().
You should also be aware, that unless datapoints() is an async function, the code you wrote WILL block the JS thread (i.e. if run in browser - it will block the UI).
Promises/deferreds with synchronous functions is not of much use.
The usual pattern would be something like this:
function datapoints() {
var d = $.Deferred()
asyncCallToCreateDatapoints(function callback(data) {
d.resolve(data)
})
return d;
}
datapoints().done(function result(data) {
/* do stuff with data */
})
Try this code:
function datapoints () {
var deferred = $.Deferred();
/* later, when data is ready, call:
deffered.resolve(data);
*/
return deferred.promise();
}
datapoints().then(function () {
// runs when data is ready
})
Not entierly sure what your problem is, but this jsfiddle is working fine for me.
function datapoints () {
//My long function here where I define $data
return {"datapoint1": 1, "datapoint2": 2};
};
var deferred = $.Deferred();
deferred.resolve(datapoints());
deferred.done(function result (data){
// My function here where I use $data
console.log("data: ", data);
});
Related
I have a javascript function that calls an AJAX, like this:
function addSquadronByID(id) {
$.ajax
({
type: "POST",
url: "server/get_squadron.php",
data: {
'id': id,
'counter': squadron_counter
},
cache: false,
success: function (data) {
squadron_counter++;
},
error: function () {
alert("AJAX error.");
}
});
}
}
Outside the document.ready, the variable is initialized like this var squadron_counter = 0;
This function perfectly works while I call it in the page, but if I try to use PHP to write it in the page, like this:
$list_squadrons = $DB->Execute($query);
while(!$list_squadrons->EOF){
$currentSquadron_id = $list_squadrons->fields["squadron_id"];
$currentSquadron_number = $list_squadrons->fields["squadrons"];
echo "addSquadronByID($currentSquadron_id);\n";
$list_squadrons->MoveNext();
}
The function writes into the document.ready() the correct calls, but squadron_counter is always zero, even if the function works. My only idea is that it works this way because javascript calls all the functions at once and does not wait to complete the first one before executing the second one, etc.. but how do I solve this?
HTML output as requested:
addSquadronByID(3, squadron_counter);
addSquadronByID(5, squadron_counter);
addSquadronByID(6, squadron_counter);
This is put into a
$( document ).ready(function() {
});
inside a <script> tag.
I think your idea about JS calling all functions without waiting for the first one to complete is in the right direction. This is called "asynchronous requests". Please refer to How to return the response from an asynchronous call? for a detailed explanation.
The idea is to send your 3 requests and then wait for all of them to complete before checking the value of your squadron_counter variable (or whatever data you have updated in your success callbacks).
Then if my understanding is correct, you do not know how to implement this waiting?
Since you are using jQuery, the implementation is super simple. Note first that your jQuery.ajax request returns a Deferred object. So simply keep a reference of the Deferred object created by each AJAX request you send. Then you could use for example jQuery.when with a callback in its then method to wait for all your requests to complete:
function addSquadronByID(id) {
return jQuery.ajax({ /* ... */ }); // returns a Deferred object.
}
var d1 = addSquadronByID(3),
d2 = addSquadronByID(5),
d3 = addSquadronByID(6);
jQuery.when(d1, d2, d3).then(
// callback executed on success of all passed Deferred objects.
function () {
console.log(squadron_counter);
}
);
Demo: http://jsfiddle.net/btjq7wuf/
Is such pattern possible in jQuery or javascript?:
$.when(function(){
//I init many plugins here, some of them use ajax etc but I dont really control it
//I only do something like $(div).somePlugin() here
$("div").myPlugin()
}).done(function(){
//and this part I want to be executed when all ajaxes and deferred stuff from when part is done
//however I cannot go to every plugin and add something like deferred.resolve() etc.
});
and myPlugin would have for example
$.fn.myPlugin = function(){
$(this).load(someUrl);
};
(but I cannot change myPlugin as its some external code.)
Basically I've got a lot of stuff happening and a lot of this uses async. functions. I want to execute some function when all this async. stuff is done, but I cannot change plugins code so I can't add .resolve() stuff to it.
Yes, this is basically what .when does!
// changes body html
var fisrtApi = $.get("http://something/foo").then(function(r){ $("body div").html(r); });
// inits some API for usage
var secondApi = somePromiseReturningFucntion();
// sets a plugin on top of a page
var somePlugin = someOtherPromiseReturningFn();
$.when(firstApi,secondApi,somePlugin).done(function(r1, r2, r3){
// all of them ready, results are the arguments
});
It is also pretty straightforward to convert a regular non promise returning API to promises.
For example, let's do $(document).ready(function(){
// returns a promise on the document being ready
function whenDocumentReady(){
var d = $.Deferred();
$(document).ready(function(){ d.resolve(); });
return d.promise();
};
Which would let you do:
$.when($.get("http://yourAPI"), whenDocumentReady()).done(function(apiResult,_){
// access API here, the document is also ready.
});
For example - with jQuery twitter, the library provides a callback for when it's done fetching data. You would promisify it:
function getTweets(username, limit){
var d = $.Deferred();
$.twitter(username, limit , function(res){ d.resolve(res); });
return d.promise();
}
Which would let you do:
$.when(getTweets("someusername"),whenDocumentReady()).done(function(tweets){
// document is ready here _and_ the twitter data is available,
// you can access it in the `tweets` parameter
});
If that is what you are looking for, then yes, it is totally possible
$.when(sync(), async(), ajax()).done(function(s,a1, a2) {
console.log( s + ' + ' + a1 + ' + ' + a2) // outputs sync + async + ajax
})
function sync() {
return 'sync'
}
function async() {
var d = $.Deferred();
setTimeout(function() {
d.resolve('async')
}, 100)
return d;
}
function ajax() {
return $.post('http://jsfiddle.net/echo/html/', { html: 'ajax' })
}
I guess the only way to do it is kind of ugly.
If you cannot use deferreds and resolve method, you have no other choice than listen to changes in the dom or context (plugins usually modify the DOM or create new object in the context).
Then you will have to look for $(myElt).hasClass('<class_created_and_applied_by_my_plugin>') turning from false to true, or stuff like this.
You have to create a deferred for each plugin and wrap the previous test in a setInterval to simulate a listener, and finally resolve the deferred.
This way, you can put all your deferred into a when and be sure they are all resolved before going on.
But this is really really uggly cause you have to personalize the test for each plugin.
And I guess, this will certainly slow down the browser too.
Here's what I'm trying to do.
I'm currently using node.js and one of the things it allows you to do is:
socket.on("sometopic", function() {
// this is a callback
}
Now let's say I have 10 different topics, each of these with one corresponding handling function that I keep in my "window", as such:
windows.callbacks["topic_a"] = function() {
// code for cb a
}
windows.callbacks["topic_b"] = function() {
// code for cb b
}
windows.callbacks["topic_z"] = function() {
// code for cb z
}
And there's a piece of code I would like to have executed at the end of every callback. An easy way out is to create a function with this code and add a call at the end of each callback but this is far from being elegant.
Can anyone suggest a better solution for this? Is there a best practice that I'm unaware of? I'm fairly green to this kind of functional programming.
// THIS IS AN IDEA
// helper
function teardownWith(fn){
return function (cb){
return function(){
return (cb(), fn());
};
};
}
// prepare a modified function
var endWithDate = teardownWith(function(){
log(Date());
});
// pass our callback into the modified function
// endWithDate() returns the final callback.
window.callbacks["cb_a"] = endWithDate(function(){
// code for cb_a
});
Consider using the jQuery Deferred object, and adding a method to execute 'always'
jQuery Deferred Object Documentation
I'm currently trying to get around the async behavior of AJAX. Problem is, that I have an unspecified amount of AJAX calls that I all have to wait for. I'm creating with jQuery a deferred object that gets resolved manually as soon as the last ajax call has finished including its success-handler. works fine, but: it seems that the function, where all that happens, has terminated (and cleaned up all variables that were declared inside that function) before the then() function executes. I can only solve this problem by declaring the needed variable users globally.
If I declare
$().click(function() {
/* inside here */
var users = [];
});
then it doesn't work. Console states that the var users is not declared. (See code example).
What is a clean approach to solve this problem? Declaring all needed variables globally seems not really nice to me.
Link to jsfiddle with my code example
You will need to declare the variable in a scope where all functions can access it. As your getGroupMembers function is in the global scope, so your users variable needs to be. Move it into the ready or click scope, and you can declare the variable as local.
However, it would be much easier to pass the result of your request as arguments to resolve:
$(document).ready(function() {
$('#message_send').click(function() {
var deferred = getGroupMembers({
page: 1,
per_page: 100
});
deferred.then(function(users) {
console.log(users);
});
});
});
function getGroupMembers(querydata) {
var users = [];
var deferredObject = new $.Deferred();
…
// some asynchronous tasks, callbacking either
deferredObject.resolve(users);
// or
deferredObject.reject();
…
return deferredObject.promise();
}
For some syntactic sugar, you might as well just use the pipe method of the Ajax Deferred.
Recursive piped method:
function getGroupMembers(querydata) {
return $.ajax({
url: "/echo/json/",
dataType: "json",
data: querydata,
cache: false
}).pipe(function(data) {
var user = data.response.UserActions,
users = [];
for (var u = 0; u < user.length; u++) {
users.push(user[u].user.id);
}
if (querydata.page < data.meta.total_pages) {
querydata.page++;
return getGroupMembers(querydata).pipe(function(nextusers) {
return users.concat(nextusers);
});
} else {
return users;
}
});
}
You can get around this with closures but I would suggest looking at async.js
If you want to pass the last response of an ajax call to the next function
https://github.com/caolan/async#series
Probably a better choice if you are running one ajax after an another is waterfall
https://github.com/caolan/async#waterfall
waterfall is the same as series but stops if any of the functions fail / series doesn't
In the event of running multiple ajax calls and just waiting until they are all finished
https://github.com/caolan/async#parallel
If I use a closure to define something is there a means of waiting so to speak until the variable is populated before moving on to the next bit.
Example:
var myVari = someFunction();
$.each(myVari, function(){/*code for each*/});
the function that defines myVari is an AJAX call, which can take a second or 4 (yea its not to fast) to define the variable. Problem is, before the AJAX call yields its results the $.each has already fired off and errored due to myVari being empty. Is there a better way to approach this scenario?
You should adapt your code so that you can pass a callback to someFunction, which you execute when the AJAX call is completed.
The only way you can wait for the AJAX call to complete is to change the call to synchronous, but this is heavily discouraged as it locks up the browser completely for the duration of the AJAX call.
Because you are already using the jQuery libary, this process of callbacks becomes a whole lot easier. Instead of returning the variable like you are at the moment, I'd return the jQuery AJAX object (which has a promise interface as of 1.6), so you can easily add callbacks to it:
function someFunction () {
return jQuery.ajax('some/url.php', {
// whatever
});
}
var myVari = someFunction();
myVari.done(function (data) {
$.each(data, function(){/*code for each*/});
});
If I understand what you are trying to do, then you could try your $.each inside the 'success' handler of your ajax call.
Rewrite someFunction to something like -
var myVari; //define this here or in whichever calling scope where it needs to be available.
$.ajax({
'url': 'http://..',
'type': 'GET', // or POST
'data': { } // whatever data you need to send
'success': function(data) {
myVari = process_the_server(data);
$.each(myVari, function() {...});
}
});
Use a callback, like this:
someFunction(function(myVari) {
$.each(myVari, function(){ /*code for each*/ });
});
Then redefine someFunction like this:
function someFunction(callback) {
var myVari;
/* ... */
/* calcuate myVari */
/* ... */
/* instead of returning it, pass it to the callback: */
callback(myVari);
}
The correct way is: Instead of running the each on its own, run it inside the ajax call.
You could, I suppose do:
function checkFunc() {
setTimeout(function() {
if(myVari) {
$.each(........);
} else {
checkFunc();
}
}, 1000);
}
That not really good coding practice, but it will work.