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.
Related
I'm working with a string of XMLHttpRequests that each depend on the one prior to it. Psuedocode:
xhr1.open('GET', 'http://foo.com');
xhr1.onload = function(e){
xhr2.open('POST', xhr1.response.url)
xhr2.onload = function(e){
xhr3.open('GET', xhr2.response.url2);
xhr3.onload = function(e){
console.log('hooray! you have data from the 3rd URL!');
}
xhr3.send();
}
xhr2.send();
}
xhr1.send();
Is this the kind of situation where using promises would be a good idea to avoid all the callback muck?
Yes. If you return a promise in a then, the next chained then listens for that promise instead of resolving from the original promise. Given that ajaxCall returns a promise, your code will then look like:
ajaxCall(1)
.then(function(result1){
return ajaxCall(2);
})
.then(function(result2){
return ajaxCall(3);
})
.then(function(result3){
// all done
});
// Sample AJAX call
function ajaxCall(){
return new Promise(function(resolve, reject){
// xhr code. call resolve/reject with accordingly
// args passed into resolve/reject will be passed as result in then
});
}
Yes, definitively. Assuming a helper function like those from How do I promisify native XHR?, your code could be transformed into
makeRequest('GET', 'http://foo.com').then(function(response1) {
return makeRequest('POST', response1.url);
}).then(function(response2) {
return makeRequest('GET', response2.url2);
}).then(function(response3) {
console.log('hooray! you have data from the 3rd URL!');
});
Still callbacks of course, but no more nesting required. Also you'd have simple error handling, and the code looks much cleaner (partially contributed to by the non-promise-related fact of abstracting XHR in its own function).
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);
});
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
Kept on seeing this pattern in code, but couldn't find any reference to it in google or SO, strange. Can someone point me to reference for this.async() function?
var done = this.async();
// ...
$.get(path, function(contents) { // or some other function with callback
// ...
done(JST[path] = tmpl);
})
var done = this.async() and done(blah) is a clever trick to return a value fetched from asynchronous call (e.g. $.get) within a synchronous function.
Let's see an example:
var getText = function() {
return "hello";
};
var text = getText();
It's a pretty straightforward function call so no puzzle here. However, what if you need to fetch the text asynchronously in getText() function?
var getText = function() {
return $.get('<some-url>', function(text) {
return text;
}); // ??????
};
call to getText() doesn't return the text you want to get. It returns jquery's promise object.
So how do we make getText() return the text it gets from $.get() call?
var getText = function() {
var done = this.async();
$.get('<some-url>', function(text) {
done(text);
});
};
var text = getText(); // you get the expected text
Magic, right?
I don't know the inner-working of this.async() call yet. I don't know if there is a library provides that function, but you can see that Backbone.LayoutManager uses this trick https://github.com/tbranyen/backbone.layoutmanager/blob/master/backbone.layoutmanager.js (search for this.async).
Also, Tim Branyen (the author of backbone layoutmanager) briefly talks about it in his video tutorial (http://vimeo.com/32765088 around 14:00 - 15:00). In the video, Tim says Ben Alman came up with that trick. Take a look at this as well https://github.com/cowboy/javascript-sync-async-foreach
I think it's a pretty neat trick to mix async and sync functions.
Cheers,
var done = this.async() is a pattern used in Grunt to help perform asynchronous functions within a Task.
You need to invoke done() or done(returnValues) to tell Grunt the task is complete (after your chain of asynchronous tasks).
Read more about it:
https://gruntjs.com/inside-tasks#inside-all-tasks
It is a way to work around the problem of this escaping inside callback. Without this extra reference the code would look like this:
$.get(path, function(contents) { // or some other function with callback
//Wrong! `this` might no longer point to your object
this.done(JST[path] = tmpl);
})
Unfortunately! this inside response callback is not the same as this outside of it. In fact it can be anything, depending on what $.get (calling the callback using) decides it to be. Most of the people use extra reference named that for the same purpose:
var that = this;
// ...
$.get(path, function(contents) { // or some other function with callback
// ...
that.async(JST[path] = tmpl);
})
This pattern also seems reasonable and readable.
Oh, and if you are curious about this syntax:
done(JST[path] = tmpl)
This is an assignment used as an expression. The value of assignment is the right-hand side, so this code is equivalent to:
JST[path] = tmpl;
done(tmpl);
I am refactoring a resource-loading function that used a traditional callback pattern to instead use jQuery Deferreds.
This function takes and array of urls, creates a new Deferred object for each resource, creates a $.when Deferred object to watch them, and returns the promise of the $.when object.
Here's a simplified version of the method:
theLib = {
getResources : function(paths) {
var deferreds = [];
theLib.isLoading = true;
$.each(paths, function(i, path) {
// do something with the path, either using getScript
// or making a new $.Deferred as needed
deferreds.push(/* the deferred object from above comment */);
});
theLib.currentDeferred = $.when.apply(null,deferreds).always(function() {
theLib.isLoading = false;
});
return theLib.currentDeferred.promise();
};
This works well.
My problem: In the old script, not only would one call theLib.getResources() based on user actions or events, but one would also define a master list of resources that the application will "stream" while the user is not taking any action (i.e. reading an article).
Some of these streamed resources would be the same resources that could be called manually when a user does take action. The script was smart enough not to load a resource twice by keeping track of what was loaded.
It also keeps track of theLib.isLoading. The beginning of that function looked something like this:
getResources : function(paths, callback) {
if (theLib.isLoading) {
settimeout(function() {
theLib.getResources(paths, callback);
}, 100);
}
theLib.isLoading = true;
I can't do this anymore, because I need to return a promise object.
I know that I can check theLib.currentDeferred.isResolved(). At that point, if it is not resolved: How can I add more deferred objects to the $.when queue that is being watched?
I guess I needed to ask the question to find a solution for myself. Basically I added the following code the beginning of getResources:
if ( ! theLib.currentDeferred.isResolved()) {
return $.when(theLib.currentDeferred).always(function() {
theLib.getResources(paths);
}).promise();
}
The above was failing. The correct solution was to pipe the results:
if ( ! theLib.currentDeferred.isResolved()) {
return theLib.currentDeferred.pipe(function() {
return theLib.getResources(paths);
});
}