I have a function. The inside of the function looks like:
if (isNewCustomer) {
doSomething();
cleanup();
}
else {
$.getJSON(..., function(result) {
doSomethingElse();
cleanup();
});
}
I was hoping I could simply this by using deferred. My attempt looks like:
var do_it = doSomething;
if (!isNewCustomer) {
do_it = $.getJSON(..., function(result) {
doSomethingElse();
});
}
$.when(do_it).done(function() {
cleanup();
});
But this isn't working. What am I doing wrong?
EDIT: Renaming variable do to do_it. This isn't the problem with the code. The problem is that when do_it is doSomething, doSomething doesn't get executed.
do is a keyword in javascript, so better rename the variable.
var do_it = doSomething;
if (!isNewCustomer) {
do_it = $.getJSON(..., function(result) {
doSomethingElse();
});
}
// || << note me
$.when(do_it()).done(function() {
cleanup();
});
var result;
if (isNewCustomer) {
result = doSomething();
} else {
result = $.getJSON( ..., function( data ) {
doSomethingElse( data );
});
}
$.when( result ).done(function() {
cleanup();
});
See the code above: you never called the function just like Gigi pointed out.
Check out this jsfiddle
https://jsfiddle.net/timlint/tg7xqtha/
Using Deferred is the way to go. It's a little hard to grasp the flow sometimes and how to pass data around but this example may give you some insight.
You can almost think of a deferred as a flag. in a function you create a deferred object.
the function returns the .promise() for that deferred. this allows you to call the function doSomething(bool).done() and do something once it finishes. You resolve the deferred when you know the task is complete and it won't be called until then.
function doSomething(isNewCustomer)
{
// think of a deferred as a flag object in a way
var d = $.Deferred();
if(!isNewCustomer)
{
$.getJSON(..., function(result) {
// work with data
}).done(function() {
// finisn up data
// set deferred to resolved
d.resolve();
});
}
else
{
// some business logic
// set deferred to resolved
d.resolve();
}
// returning the promise lets you call .done()
// on this function call in the main call
return d.promise();
}
You need an explicit Deferred. If you pass when() an argument that is not a Deferred, the function is invoked immediately, and is probably why you're getting unexpected results.
var deferred = $.Deferred();
if (isNewCustomer) {
deferred.resolveWith(doSomething());
}
else {
$.getJSON(...).
done(function(result) {
deferred.resolveWith(doSomethingElse(result));
}).
fail(function(...) {
deferred.rejectWith(...);
});
}
deferred.promise().always(function() { cleanup(); });
Related
Ok, so I've read 1,000,000+ articles on jQuery deferreds and/or promises, and I'm still getting something wrong.
functionOne() {
var deferred = $.Deferred();
var request = $.ajax({
url: 'http://example.com/mypath/etc'
});
request.done(function(data) {
// TODO: I've got stuff here that takes a while.
deferred.resolve();
});
return deferred.promise();
}
functionTwo() {
// Something that depends on the others being finished.
}
$.when(
functionOne(),
anotherLongRunningFunctionWithAjax())
.then(
functionTwo()
);
I need any function(s) in the "when" to fully complete (.ajax done) before the "then" starts. However, the promise returns immediately (as expected), but functionTwo starts, even though functionOne has not called "done".
I'm sure it's a fundamental misunderstanding of deferred and the chain-of-calls.
Edit:
function functionOne() {
console.log('functionOne called');
var request = $.ajax({
url: 'http://example.com/mypath/etc'
});
request.done(function(data) {
console.log('Starting Done.');
setTimeout(function () {
console.log('Done finished.');
}, 5000);
});
console.log('Returning promise.');
return request;
}
function functionTwo() {
console.log('functionTwo called');
}
$.when(functionOne()).then(functionTwo());
Gives me this in the console:
functionOne called
Returning promise.
functionTwo called (should be called after Done is finished.)
Starting Done.
Done finished.
Taking the code in your edit, there are two issues:
The timer in functionOne starts after request is resolved, yet you return request. So whatever happens with the timer... it is of not relevance to the returned promise, which at that time is already resolved.
You call functionTwo immediately, instead of passing the function reference for the $.when promise to call back
Here is working code:
function functionOne() {
console.log('functionOne called');
console.log('Returning promise.');
return $.ajax({
url: 'https://jsonplaceholder.typicode.com/posts/1'
}).then(function(data) {
console.log('Starting Done.');
var dfd = $.Deferred();
setTimeout(function () {
console.log('Done finished.');
dfd.resolve(data); // indicate when you are done
}, 2000); // 2 seconds will do ;-)
return dfd.promise(); // you need to return a promise again
});
}
function functionTwo() {
console.log('functionTwo called');
}
// don't call functionTwo immediately, leave that to the promise to do:
$.when(functionOne()).then(functionTwo);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
you are using an anti-pattern since $.ajax itself returns a promise
Just do
functionOne() {
var request = $.ajax({
url: 'http://example.com/mypath/etc'
});
request.done(function(data) {
// TODO: I've got stuff here that takes a while.
});
return request
}
I have a function that uses two ajax calls in order to get the proper information:
var getUsers = function() {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(foo) {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(bar) {
return foo['age'] = bar.type;
});
});
}
And an outside function that calls the current function and only continues when the calls are finished.
getUsers().then(function(result) {
// ...
});
Now the weird thing is that if I display the result, the 'age' will show up in the console, but if I try to access it using result['age'], it will return undefined.
Is there a proper way of handling multiple deferred calls?
Code
http://codepen.io/norbiu/pen/bNRQxL
Edit Instead of using a separate deferred, you can chain the ones returned from getJSON() like this
var getUsers = function() {
var foo;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.then(function(data) {
foo = data;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
}).then(function(bar) {
foo['age'] = bar.type;
return foo;
});
}
Note: you need to save the return value from the first call or it won't be accessible to the second.
Original code for posterity
You can use a jQuery Deferred object and return that instead
var getUsers = function() {
var dfd = $.Deferred();
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(foo) {
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(bar) {
foo['age'] = bar.type;
dfd.resolve(foo);
}).fail(function(e) {
dfd.reject(e);
})
}).fail(function(e) {
dfd.reject(e);
});
return dfd.promise();
}
http://codepen.io/anon/pen/pvwqZo
The deferred object won't resolve until both requests succeed (and will fail if any of them fail).
I am trying to listen to an array of deferred requests for completion. I want to add in fallbacks so that if the initial url fails, then it will load a json file. (I've loaded the jsbin page here to stop any cross domain issues).
My original code was something like
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail(function() {
$.get("http://run.jsbin.com/")
.done(function() {
statsDeferred.resolve();
});
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
However it fails at the line statsDeferred.resolve();
http://jsbin.com/pofejotu/1/
I have tried adding in $.proxy calls to maintain scope but it isn't working.
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail($.proxy(function() {
$.get("http://run.jsbin.com/")
.done($.proxy(function() {
statsDeferred.resolve();
}, this));
}, this));
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
http://jsbin.com/vonibuhe/1/edit
Both fail on
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
If you want to chain promises, the correct method to use is .then() :
function makeCalls () {
var statsDeferred = $.get("http://thiswillfail.yesitwill");
statsDeferred = statsDeferred.then(
null, /* on success, keep the initial promise's state */
function(){ return $.get("http://run.jsbin.com/"); }
);
return statsDeferred;
}
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
The error you have is the difference between a Deferred and a Promise.
a Deferred exposes methods to change its inner state (.resolve and .reject),
a Promise only allows you to consult this state, and react on it (.done, .fail, ...)
API functions will generally return a Promise, so that external users cannot meddle with the expected state. As an example, one way to "fix" your code would be the following :
function makeCalls() {
// make a deferred, you will be the one in control of its state :
var deferred = $.Deferred();
var firstGet = $.get("http://thiswillfail.yesitwill");
firstGet.done(function(response) { deferred.resolve(response); })
// if the first request fails, run the second :
firstGet.fail(function(){
var secondGet = $.get("http://run.jsbin.com/");
secondGet.done(function(response) { deferred.resolve(response) };
secondGet.fail(function() { deferred.reject() });
});
// only return the Promise to the outer world :
return deferred.promise();
}
I was reading about promises and found this fiddle created by the author of this post
The code is here:
var def, getData, updateUI, resolvePromise;
// The Promise and handler
def = new $.Deferred();
updateUI = function (data) {
$('p').html('I got the data!');
$('div').html(data);
};
getData = $.ajax({
url: '/echo/html/',
data: {
html: 'testhtml',
delay: 3
},
type: 'post'
})
.done(function(resp) {
return resp;
})
.fail(function (error) {
throw new Error("Error getting the data");
});
// Event Handler
resolvePromise = function (ev) {
ev.preventDefault();
def.resolve(ev.type, this);
return def.promise();
};
// Bind the Event
$(document).on('click', 'button', resolvePromise);
def.then(function() {
return getData;
})
.then(function(data) {
updateUI(data);
})
.done(function(promiseValue, el) {
console.log('The promise was resolved by: ', promiseValue, ' on ', el);
});
// Console output: The promise was resolved by: click on <button> </button>
I do understand from the first part of this series that a deferred has a promise which can be exposed using the promise method on it.
Promises have then method which returns a promise for chaining.
Here they resolve the promise on the deferred in resolvePromise,then the then method on the deferred which I dont think is a promise is executed.What am I missing here?
Deferred objects in jQuery are also thenables and you can use them in place of promises. Doing so is rather uncommon though.
var d = $.Deferred().resolve();
d.then(function(){
console.log("HI"); // this will run.
});
The original $.ajax having .done and .fail is pointless in this case, especially the .done whose return value is ignored and has no impact.
In all honestly, I think the code could be improved to something like rather easily:
var getData = $.post('/echo/html/', { html: 'testhtml', delay: 3 });
var d = $.Deferred();
$(document).on('click', 'button', function(ev){
d.resolve();
return false;
});
$.when(d, getData).then(function(_, data){
$('p').html('I got the data!');
$('div').html(data);
});
There is no point in .thening if you only use the identity function (that is, return the same thing and do nothing else.
There is no point in .doneing only to return the same thing.
Generally, I would advise against promises for handing events unless the events are strictly one time.
I'm having a problem with callback functions in javascript. What I want to do is: loop on a for and call a function passing i as parameter. With that in mind, I have to loop to the next interaction only after the previous one has been finished. I don't know if this is a problem but inside the function I'm sending i as parameter, I have another callback function. Here is my code:
for(i=0; i<10; i++) {
aux(i, function(success) {
/*
* this should be made interaction by interaction
* but what happens is: while I'm still running my first interaction
* (i=0), the code loops for i=1, i=2, etc. before the response of
* the previous interaction
*/
if(!success)
doSomething();
else
doSomethingElse();
});
}
function aux(i, success) {
... //here I make my logic with "i" sent as parameter
getReturnFromAjax(function(response) {
if(response)
return success(true);
else
return success(false);
});
});
function getReturnFromAjax(callback) {
...
$.ajax({
url: myUrl,
type: "POST",
success: function (response) {
return callback(response);
}
});
}
jQuery's Deferred can be a bit tricky to get right. What you'll have to do is stack your promises in a chain. For example:
var
// create a deferred object
dfd = $.Deferred(),
// get the promise
promise = dfd.promise(),
// the loop variable
i
;
for(i = 0; i < 10; i += 1) {
// use `then` and use the new promise for next itteration
promise = promise.then(
// prepare the function to be called, but don't execute it!
// (see docs for .bind)
aux.bind(null, i, function(success) {
success ? doSomethingElse() : doSomething();
})
);
}
// resolve the deferred object
dfd.resolve();
for this to work, aux must also return a promise, but $.ajax already does this, so just pass it through and everything should work:
in aux:
function aux(i, callback) {
console.log('executing for `aux` with', i);
// return the ajax-promise
return getReturnFromAjax(function(response) {
callback(Boolean(response));
});
}
in getReturnFromAjax:
function getReturnFromAjax(callback) {
// return the ajax-promise
return $.ajax({
url: '%your-url%',
type: '%method%',
success: function (response) {
callback(response);
}
});
}
demo: http://jsbin.com/pilebofi/2/
I'd suggest that you'd look into jQuery's Deferred Objects and jQuery.Deferred()-method instead of making your own callback queue functions (as you are already using jQuery anyway).
Description: A constructor function that returns a chainable utility
object with methods to register multiple callbacks into callback
queues, invoke callback queues, and relay the success or failure state
of any synchronous or asynchronous function.
I don't have experience with jQuery, but your callback looks a bit fishy to me.
In plain JS I'd suggest trying something among the lines of this:
function yourMainFunction
{
function callbackHandler(result)
{
// Code that depends on on the result of the callback
}
getAjaxResults(callbackHandler);
}
function getAjaxResults(callbackHandler)
{
// Create xmlHttpRequest Handler, etc.
// Make your AJAX request
xmlHttp.onreadystatechange = function()
{
if (xmlHttp.readyState == 4 && xmlHttp.status==200)
{
// Do stuff you want to do if the request was successful
// Define a variable with the value(s) you want to return to the main function
callbackHandler(yourReturnVariable);
}
}
}