Chaining jQuery promises/deferreds - javascript

I have a process where I have to send an ajax request to the server, but need to stop a scheduler before the ajax request begins, then restart the scheduler when the request is done.
I have got this working using the following code:
scheduler.stop()
.done(function () {
setQuestionStatus().done(scheduler.start);
});
But it seems that there should be a simpler way to write this, such as:
scheduler.stop().then(setQuestionStatus).then(scheduler.start);
My problem is that when written this way, setQuestionStatus and scheduler.start are both called as soon as scheduler.stop has resolved, rather than after each item in the chain has resolved.
Can anyone tell me what I'm doing wrong in the second example?
For your information, both scheduler.stop and setQuestionStatus return a promise using the pattern:
var setQuestionStatus = function(){
return $.Deferred(function (def) {
// Do stuff
def.resolve();
}).promise();
}

scheduler.stop().then(setQuestionStatus).then(scheduler.start);
My problem is that when written this way, setQuestionStatus and scheduler.start are both called as soon as scheduler.stop has resolved, rather than after each item in the chain has resolved.
That's ugly non-standard behavior found in earlier versions of jQuery. Either update your copy to 1.8+ for using then, or use the pipe method instead.

Related

Confusion between jQuery Deferrable, jsDeferred, and just deffering in general

I downloaded a library called jsdeferred to try and help me with some code-flow problems, but I am a little lost, as its examples and ...'documentation' is a little unclear on some things. But as I kept reading and digging, and of course googling everything under the sun, I also found out jQuery has its own Deferred() system. I am linking both here, for proper context.
Link to jsDeferred Library
Link to jQuery.Deferred()
The Problem
I need to find a way to tell the page to "hold on until the last thing is done".
This is what thought jsdeffered did. So part of my question is asking which should I use? jsDeferred or jQuery.Deferred(); and then how to use it as I've outlined below.
The Situation
My scenario is this, in a nutshell, I need to perform the following behavior.
page loads, a view model is defined
This is using kendo ui mvvm to declare my view model, so it is a kendo.data.ObservableObject
an $.ajax call is made to the database to get some default model data
This is where I am getting the most trouble. I need everything to "hold on" until this $.ajax is done. But I don't want to wrap everything in the $.ajax().done(r) if I can help it. That looks/feels very sloppy to me and is kind of confusing at times.
other widgets on the page are rendered, they have respective database queries done through kendo ui Remote DataSource.
These are actually working as intended.
jQuery Validate is wired to the view, with defaults having been set already.
This is also working as intended.
kendo.bind('body', viewModel); is called to perform model binding.
Now this is where I am running into trouble, going back to step 2 where I was making the $.ajax call. What keeps happening is that kendo.bind is fired before the $.ajax completes. I can put it in the $.ajax({}).done(); function, and for this exact one specific page that does work, but there will be many other situations where that isn't suitable.
What I have tried
First, I'll be clear that the jsdeferred documentation is very unclear to me, as running its samples verbatim doesn't actually work. I am continuously told that next is not defined and the like. I eventually figured out that you have to have an implicit Deferred. before you call next the first time.
So here is what I thought would happen...
var viewModel = new kendo.data.ObservableObject({
// various view model properties defined
});
Deferred.define();
next(function() { // let's call this STEP 1
$.ajax({
// data for ajax to controller
}).done(function(result) {
// perform operations with result
});
}).
next(function() { // let's call this STEP 2
$('#dropdownlist_target').kendoDropDownList({
// parameters, remote data source for drop down list, etc.
}).data("kendoDropDownList");
}).
next(function() { // let's call this STEP 3
$('form').validate({
// any extra form validation stuff
});
}).
next(function(){ // let's call this STEP 4
kendo.bind('body', viewModel);
});
I believed that these would each run one, right after the other, when the previous one is finished. But that is not what is happening. STEP 1 is still in the process of fetching while STEP 2, 3 and 4 are running.
This doesn't seem to be any different than the way the code was running without the jsdeferred library. So I am very confused and would absolutely love some help here. I need STEP 1 to be completely finished before STEP 2 fires, basically.
The problem is that next() expects you to return the thing you want it to wait for. In step one, you're not returning anything. jsdeferred is therefore assuming you were performing a synchronous operation (that has already finished), and so it continues with step 2.
Instead, return the jQuery.Deferred() returned from the $.ajax() call. jsdeferred will then wait for that to complete before it executes step 2.
Regardless of this, I'd dump jsdeferred. As you've realised, jQuery has a fully fledged Deferred implementation. I'm not sure what jsdeferred brings to the party.
Using $.ajax().done(r) is not sloppy. Asynchronous behaviour is the core of event driven languages, and JavaScript is one. Embrace it, or you'll go bald very early in life trying to avoid it.
If you revert to jQuery's Deferred implementation, you might like then(), to give you the semantics of next();
$.ajax({
// data for ajax to controller
}).done(function(result) {
// perform operations with result
}).then(function () {
$('#dropdownlist_target').kendoDropDownList({
// parameters, remote data source for drop down list, etc.
}).data("kendoDropDownList");
$('form').validate({
// any extra form validation stuff
});
kendo.bind('body', viewModel);
}).then(function () {
// Note you can chain then()'s as well.
});
You can just use the then method on your $.ajax() result in the same way you're using jsDeferred's next helper. Generally speaking, then is a more flexible method than done. And as Matt noted in his answer, it's a common mistake in promise based programming to forget to return a new promise within the handler, causing it to resolve prematurely with undefined instead of waiting on a the new promise.
$.ajax({ // let's call this STEP 1
// data for ajax to controller
}).
then(function(result) {
// perform operations with result
}).
then(function() { // let's call this STEP 2
$('#dropdownlist_target').kendoDropDownList({
// parameters, remote data source for drop down list, etc.
}).data("kendoDropDownList");
}).
then(function() { // let's call this STEP 3
$('form').validate({
// any extra form validation stuff
});
}).
done(function(){ // let's call this STEP 4
kendo.bind('body', viewModel);
});
Note that in my refactoring, all of those thens will execute immediately in a row, unless a new promise is returned. So you may as well combine them.
then takes a function that either returns a value or a promise, and it returns a new promise. If its function returned a value, the new promise is immediately resolved with that value. If its function returned a promise, then that promise is passed through as the new promise. Note that jQuery's then only works this way as of jQuery versions >=1.8.

Understanding jQuery.Deferred in the context of jQuey.AJAX (again)

I concede that, despite hours of reading and attempting, I am fundamentally unable to grasp something about Deferred promises and asynchrony in general.
The goal on my end is real, real simple: send some data to the server, and react to the contents of the response conditionally.
The response will always be a JSON object with save and error keys:
{ "save": true, "error":false}
// or
{ "save" : false,
"error" : "The server has run off again; authorities have been notifed."}
I have tried dozens and dozens of variations from the jQuery API, from other stackexchange answers, from tutorials, etc.. The examples all seem concerned with local asynchronous activity. When I need is some ability to be made aware when the AJAX request has either finished and returned a response I can inspect and make decisions about, or else to know that it's failed. Below, I've used comments to explain what I think is happening so someone can show me where I'm failing.
I know this is a repost; I am, apprently, worse than on average at grasping this.
var postData = {"id":7, "answer":"Ever since I went to Disneyland..."};
/* when(), as I understand it, should fire an event to be
responded to by then() when it's contents have run their course */
var result = $.when(
/* here I believe I'm supposed to assert what must complete
before the when() event has fired and before any chained
functions are subsequently called */
/* this should return a jqXHR object to then(), which is,
I'd thought, a queue of functions to call, in order,
UPON COMPLETION of the asynchronous bit */
$.post("my/restful/url", postData))
.then( function() {
/* since "this" is the jqXHR object generated in the $.post()
call above, and since it's supposed to be completed by now,
it's data key should be populated by the server's response—right? */
return this.data;
});
// alas, it isn't
console.log(result.data);
// >> undefined
Most examples I can find discuss a timeout function; but this seems, as I understand, to be a failsafe put in place to arbitrarily decide when the asynchronous part is said to have failed, rather than a means of stalling for time so the request can complete. Indeed, if all we can do is just wait it out, how's that any different from a synchronous request?
I'll even take links to a new read-mes, tutorials, etc. if they cover the material in a different way, use something other than modified examples from the jQuery API, or otherwise help this drooling idiot through the asynchronous mirk; here's where I've been reading to date:
jQuery API: Deferred
JQuery Fundamentals
jQuery Deferreds promises asynchronous bliss (blog)
StackOverflow: timeout for function (jQuery)
Update
This is in response to #Kevin B below:
I tried this:
var moduleA = {
var moduleB = {
postData: {"id":7, "answer":"Ever since I went to Disneyland..."};
save: function() {
return $.post("path/to/service", postData, null, "JSON");
}
};
var result = this.moduleB.save();
result.done(function(resp) {
if (resp.saved == true) {
// never reached before completion
console.log("yahoo");
} else {
console.log("Error: " + resp.error);
// >> undefined
}
});
}
You are over-complicating your code. You cannot get the data to outside of the callback, no matter how many deferred/promises you create/use (your sample creates 3 different deferred objects!)
Use the done callback.
var postData = {"id":7, "answer":"Ever since I went to Disneyland..."};
$.post("my/restful/url", postData).done(function (result) {
console.log(result.save, result.error);
});
You seem to have a misunderstanding of both asynchronous requests, the Promise pattern, and Javascripts mechanism of passing functions as an argument.
To understand what's really happening in your code I suggest you use a debugger and set some breakpoints in the code. Or, alternatively, add some console.logs in your code. This way you can see the flow of the program and might understand it better. Also be sure to log the arguments of the function you pass as an argument in the then()-method, so you understand what is passed.
ok you got it half right. the problem is that when you execute the console.log the promised is not yet fulfilled the asynchronous nature of the promises allows the code to execute before that ajax operation is done. also result is a deferred not a value, you need to handle your promised with .done instead of .then if you wish to return a value otherwise you'll continue passing promises.
so that said
var result={};
$.when(
$.post("my/restful/url", postData))
.done( function(data) {
result.data=data;
});
// here result is an object and data is a undefined since the promised has no yet been resolve.
console.log(result.data);

Javascript - Issue with promises, then() and done() methods in chain

I'm having some issues with the chaining of deferred objects. So I think that Im missing some understanding. My code is something like follow:
var AJAX_FUNC_CREATE_ALIAS = function(){
return $.when(ajax_call()).then(function(response){
// DO something with the response I get to compose an object
return composed_response;
});
}
var name = 'Alejandro',
alias = 'Ali';
$.when(AJAX_FUNC_CREATE_NAME)).then(function(response, status, jqXHR){
return AJAX_FUNC_CREATE_ALIAS(name); // <-- Wait correctly
},function(jqXHR, status, errorThrown){
return default_response;
}).done(function(artist_response){
var promise = AJAX_FUNC_CREATE_ALIAS(alias); // <----- Problematic one
console.log(promise.state()); // It shows pending state
return promise;
}).done(function(alias_response){
$.publish(channel, [alias_response])
});
The execution goes like this:
The AJAX_FUNC_CREATE_NAME function start execution. When it finishes it goes to the callback defined inside the then().
It executes the AJAX_FUNC_CREATE_ALIAS(name) function. The .done() method is not executed until the AJAX_FUNC_CREATE_ALIAS(name) has finished.
It start executing AJAX_FUNC_CREATE_ALIAS(alias); In here it does not wait to get answer from the server. It goes straight to the $.publish(....)
Why?
Update: I have added some code for checking the answer I get from the problematic line. The promise I get back seems to have 'pending state'. From the jquery documentation .done() methods : "Add handlers to be called when the Deferred object is resolved." [http://api.jquery.com/deferred.done/] . If the state is pending... why is the done() method getting it?
then returns a new promise,done doesnt.
If you want to chain tasks use then.that's what you did for the second promise,it only makes sense to do the same for the next one.
It's written right here:
http://api.jquery.com/deferred.then

Returning angular promise from a function that resolves the promise very quickly

I am writing an asynchronous javascript function that will be called by consumers to get certain data. Following is the simple implementation that I wrote initially (error handing and other stuff removed for clarity).
function getData(callback){
if (data is available as a JavaScript object){
callback(data);
}else{
getAsyncData(function(data){
//some transformations on data
callback(data);
});
}
}
What is important to note is that getData can return data quickly if data is already available as a JavaScript object.
I want to replace this implementation with the one that returns a promise object to the caller. This fiddle shows sample implementation - http://fiddle.jshell.net/ZjUg3/44/
The question - Since getData can return quickly, can there be a possiblity where getData is resolving the promise even before caller has established handler chain using then method? Just to simulate this, in the fiddle if i call then method inside setTimeout function (with zero delay), callback doesn't get called. If i call the then method outside of the setTimeout function, callback gets called. I am not sure if this is even a valid concern or valid usecase. I am quite new to angularjs development and would appreciate your views :)
If you want getData() to return a $q promise instead of using a callback, I'd do the following refactor using $q.when() and usual $q.resolve():
function getData()
{
if (data is available as a JavaScript object) {
return $q.when(data); // resolves immediately
} else {
var q = $q.defer();
getAsyncData(function(data){
//some transformations on data
q.resolve(data);
});
return q.promise;
}
}
No, a significant and important part of being a promise is that it doesn't matter when you attach the handler. Even if you create a promise now and resolve it immediately, then keep your computer running for the next 50 years, then attach a handler it will still fire.
All of this does assume that there isn't a bug/corner case in angularjs's promise implementation. If it doesn't work, it's a bug though.
If you ever need to know anything about how promises work, you can always refer to the Promises/A+ spec which angular adheers to. As a spec, it's one of the simplest and easiest to understand that I've come across (although I should mention that I've been involved in the spec for quite a while now).

Syntax for javascript object implementing an AJAX GET and custom event

I've got a couple of questions about this small snippett adapted from a tutorial I found here.
var loader = (function ($, host) {
return {
loadTemplate: function (path) {
var tmplLoader = $.get(path)
.success(function (result) {
$("body").append(result);
})
.error(function (result) {
alert("Error Loading Template");
}) // --> (1) SEMICOLON?
// (2) How does this wire up an event to the previous
// jQuery AJAX GET? Didn't it already happen?
tmplLoader.complete(function () {
$(host).trigger("TemplateLoaded", [path]);
});
}
};
})(jQuery, document);
Is there supposed to be a semicolon there?
It seems like the AJAX GET is happening and then an event is getting wired to it - what am I missing here?
Is there supposed to be a semicolon there?
It's optional, but recommended.
It seems like the AJAX GET is happening and then an event is getting wired to it - what am I missing here?
AJAX is asynchronous, so it's very unlikely the request will be already completed right after sending it. So, there's time to add another callback. And even if there weren't, it would work anyway, since jQuery implements those callbacks with promises. See example here.
With javascript, and ajax in particular it is important to understand how the browser goes about executing your code. When you make the request for remote data via an ajax GET, the rest of your code is still executing. Imagine if as soon as you made a request for some JSON to a busy server, lets say it takes a couple seconds, and everything on your page stops working during that time period. It would be very difficult to write code that wasn't difficult for the user to interact with. Luckily ajax is async, meaning it makes the request and an carries on as usual until the complete event (or equivalent) is fired. This is what executes your code pertinent to the data you just received. So when you specify that callback at the bottom of your snippit, you are telling the browser, "go do your thing for now but when you hear back from the server, do all of these things".
Oh yeah, and semicolons are optional, but as a best practice, most people use them.
They are assigning the $.get to a variable and then adding a complete handler to it.
It's the same as doing this:
$.get('/path'), function(){
//success callback
}).error(function(e){
//errors
}).complete(function(){
//always run
});
Just an unusual way of doing it.

Categories

Resources