jQuery Promise/Deferred nicer Code than callbacks? How to achieve that? - javascript

I would like to show off how much cleaner jQuery deferred objects can make the code instead of using the "callback hell".
I have no option to switch to Javascript's Promises.
Here is the "bad" code:
/* Callback Hell !? */
// Main
var stringToProcess = "1,2,3,4,5,6";
console.debug("Main Stack: start");
convertStringToArray(stringToProcess, function (convertedString){
convertToObject(convertedString, function(objectOfStrings){
resetObjectValues(objectOfStrings, function(object){
console.debug(object);
});
});
});
console.debug("Main Stack: end");
// Functions
function resetObjectValues(object, callback){
for(var key in object){
object[key] = "X";
}
setTimeout(function thirdcb(){
callback(object);
}, 500);
}
function convertToObject(string, callback){
var object = {};
string.map(function(current, index){
object[index] = current;
});
setTimeout(function secondcb(){
callback(object);
}, 500);
}
function convertStringToArray(string, callback){
var delimiter = ",";
var arrayString = string.split(delimiter);
setTimeout(function firstcb(){
callback(arrayString);
}, 500);
}
And thats how I tried to make it better:
/* Promise Heaven... */
// Main
var stringToProcess = "1,2,3,4,5,6";
console.debug("Main Stack: start");
var promise;
promise = convertStringToArray(stringToProcess);
promise.done(function(string){
promise = convertToObject(string);
promise.done(function(object){
promise = resetObjectValues(object);
promise.done(function(object){
console.debug(object);
})
})
});
console.debug("Main Stack: end");
// Functions
function resetObjectValues(object, callback){
var deferred = $.Deferred();
for(var key in object){
object[key] = "X";
}
setTimeout(function thirdcb(){
deferred.resolve(object);
}, 500);
return deferred.promise();
}
function convertToObject(string){
var deferred = $.Deferred();
var object = {};
string.map(function(current, index){
object[index] = current;
});
setTimeout(function secondcb(){
deferred.resolve(object);
}, 500);
return deferred.promise();
}
function convertStringToArray(string){
var deferred = $.Deferred();
var delimiter = ",";
var arrayString = string.split(delimiter);
setTimeout(function firstcb(){
deferred.resolve(arrayString);
}, 500);
return deferred.promise();
}
...sadly the .done() code looks almost as bad as the "hell" one. I cannot figure our how to chain the returns of promises/deferred properly. I saw tutorials where they do it without arguments to the function calls. But I have arguments to throw in - so how to get along with that?

The chaining of promises should look somewhat like this:
convertStringToArray(stringToProcess)
.then(function(string){
return convertToObject(string);
})
.then(function(object){
return resetObjectValues(object);
})
.then(function(object){
console.debug(object);
});
Basically each (callback) function returns a new promise, which can then be used to attach others handlers to it. That way you don't need the nesting of callbacks like in your code.

Related

JavaScript sequential async function calls

I have an array of functions, which making some logic asynchronously (e.g. ajax calls). How does look like function, which will sequentially call functions from array?
var saveHandlers = [];
saveHandlers.push(function () {
var deferred = $.Deferred();
setTimeout(function() {
deferred.resolve();
}, 2000);
return deferred.promise();
});
saveHandlers.push(function () {
var deferred = $.Deferred();
setTimeout(function() {
deferred.resolve();
}, 2000);
return deferred.promise();
});
$(function () {
var $form = $('#form');
$form
.unbind('submit')
.submit(function (e) {
if (saveHandlers.length > 0) {
$.when.apply(null, saveHandlers);
}
e.preventDefault();
});
});
Write a function which calls you async function and takes the array index as an argument.
In the callback for the async function, increment the index and (if you haven't got to the end of the array) call the function recursively with the new index.
var foo = [fun_a, fun_b, fun_c];
function bar(index) {
index = index || 0;
function callback() {
if (foo[++index]) {
bar(index);
}
};
foo[index]().then(callback);
}

Wait for function to execute before continuing

I am new to promises but I am trying to get a function (which returns a number) to execute before continuing on. I have tried the following and none work:
var whatNumber = function(){
var defer = $q.defer();
myNumber.get().then(defer.resolve);
return defer.promise;
}
I also tried:
var whatNumber = function(){
var defer = $q.defer();
defer.resolve( myNumber.get());
return defer.promise;
}
And finally I tried it without promises:
var whatNumber = function(){
myNumber.get().then(function(result) {
return result;
});
}
Anyone have any ideas what I am doing wrong?
You should read about deffered antipattern In your case you can just return myNumber.get(); from the function:
var whatNumber = function(){
return myNumber.get();
}
Or, if you need to return specific property from the result:
var whatNumber = function(){
return myNumber.get().then(function(result) {
return result.data;
});
}
If you want to use the antipattern method (which you should avoid), then you need to resolve the promise and pass the parameters:
var whatNumber = function(){
var defer = $q.defer();
myNumber.get().then(function(result) {
defer.resolve(result);
});
return defer.promise;
}
whatNumber().then(function(result) {
console.log(result);
});
You are close, you just have your resolve aimed incorrectly from what I can see. You need to resolve the value you plan on using (the data)
var whatNumber = function(){
var defer = $q.defer();
myNumber.get().then(function(response){
defer.resolve(response.data)
});
return defer.promise;
}

Jquery Deferred Not reaching last .then

I have a sequence of functions that must be executed. They all execute sequentially except the last one. d1 executes, d2 executes, d3 executes then the code inside the done function executes before the resolve of d4. Can't figure out why. Any help would be appreciated.
$(document).ready(function() {
var d1 = functiond1();
var d2 = functiond2();
var d3 = functiond3();
var d4 = functiond4();
d1.then(d2).then(d3).then(d4).done(function() {
//Code here does not wait for d4 to end before executing
//HELP!
});
});
function functiond1() {
var dfd = $.Deferred();
//Do stuff here
//Works in sequence
dfd.resolve();
return dfd.promise();
}
function functiond2() {
var dfd = $.Deferred();
params = jQuery.param({
'parm1': 1,
'parm2': 2,
'parm3': 3
});
jQuery.getJSON($.webMethoJSONGet1, params).done(function(data) {
//Do stuff here
//Works in sequence
dfd.resolve();
});
return dfd.promise();
}
function functiond3() {
var dfd = $.Deferred();
//Do stuff here
//Works in sequence
dfd.resolve();
return dfd.promise();
}
function functiond4() {
var dfd = $.Deferred();
params = jQuery.param({
'parm1': 1,
'parm2': 2,
'parm3': 3
});
jQuery.getJSON($.webMethoJSONGet2, params).done(function(data) {
//Do stuff here
//does not work in sequence
dfd.resolve();
});
return dfd.promise();
}
It's hard to tell what you are trying to do with those promises. You first call all 4 functions, and then you try to chain them with a bunch of then callbacks. If you want to sequentially chain them together it should look like this:
functiond1()
.then(functiond2)
.then(functiond3)
.then(functiond4)
.done(function() { /* blah */ });
If you just want a result after all have completed you can use $.when
$.when(functiond1(), functiond2(), functiond3(), functiond4())
.then(function(resultd1, resultd2, resultd3, resultd4) { /* blah */ });
On another note, in your functions you create promises that are resolved inside the done callback of another promise which is unnecessary. The $.getJSON.done() calls return a promise themselves so an additional promise is not needed. Just return the promise returned from done().
Sorry, I haven't messed much with jQuery deferred objects, but they appear similiar enough to standard Promises.
To run the functions in sequence, you need to pass references to the functions within the .then chain, and not the results of calling those functions.
e.g.
var d1 = functiond1; // NB: no ()
...
d1.then(d2).then(d3).then(d4).done(...);
functiond1().then(functiond2).then(functiond3).then(functiond4).done(...)
The ultimate cause of your problem is that invoking d4 straight away will cause its resolved promise to pass-thru to .done immediately irrespective of the state of the earlier part of the .then chain.
You should also not wrap your JSON functions with additional promises, since $.getJSON already returns a promise that will be rejected if the AJAX query fails:
function functiond4() {
...
return $.getJSON(...);
}
I was facing the same issue on a project, this solution with an array works well:
$(document).ready(function() {
var pr = [];
var d1 = functiond1();
var d2 = functiond2();
var d3 = functiond3();
var d4 = functiond4();
function functiond1() {
var dfd = $.Deferred();
pr.push(dfd);
setTimeout(function(){
$('body').append('1 resolved <br>');
dfd.resolve();
}, 2000);
}
function functiond2() {
var dfd = $.Deferred();
pr.push(dfd);
params = jQuery.param({
'parm1': 1,
'parm2': 2,
'parm3': 3
});
setTimeout(function(){
$('body').append('2 resolved <br>');
dfd.resolve();
}, 3000);
}
function functiond3() {
var dfd = $.Deferred();
pr.push(dfd);
setTimeout(function(){
$('body').append('3 resolved <br>');
dfd.resolve();
}, 1000);
}
function functiond4() {
var dfd = $.Deferred();
pr.push(dfd);
params = jQuery.param({
'parm1': 1,
'parm2': 2,
'parm3': 3
});
setTimeout(function(){
$('body').append('4 resolved <br>');
dfd.resolve();
}, 50);
}
$.when.apply($, pr).then(function() {
// do something
$('body').append('proceed with code execution');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

How do you return a value from one class method to another when using Q/promises/asynchronous functions?

What is the best way to return a value from one class method to a different class method when using Q/promises/asynchronous functions?
Specifically, I have the following where ClassOne.myMethod() will call ClassTwo.test() to perform several asynchronous tasks (db updates, file writes, etc). I would like ClassTwo.test() to return something (in this example, "FOUR"). How do I do that when using promises and asynchronous calls?
I am doing this because I want ClassTwo to be a very generic set of methods that perform tasks that will be called by other classes (as not to reinvent the wheel each time).
E.g.,
var myClass = new ClassTwo();
ClassOne.prototype.myMethod = function(myClass) {
console.log('Returns: ', myClass.test());
};
ClassTwo.prototype.test = function() {
var one = function() {
var deferred = Q.defer();
console.log('ONE');
deferred.resolve();
return deferred.promise;
};
var two = function() {
var deferred = Q.defer();
console.log('TWO');
deferred.resolve();
return deferred.promise;
};
var three = function() {
var deferred = Q.defer();
console.log('THREE');
deferred.resolve();
return 'FOUR';
};
return one()
.then(two)
.then(three);
};
I think you are looking for something like the following. Note that I have wrapped all the calls to deferred.resolve() into callbacks from asynchronous functions (in this case process.nextTick), since that would be a more realistic use case then resolving the promise before returning it, and is, I assume what you would be doing with your asynchronous tasks. Also you declare a variable 'myClass' and also use the same identifier as a function parameter for 'myMethod'.I don't think that is really what you meant to do, so I have changed that in my example below.
var ClassTwo = function() {};
var ClassOne = function() {};
var Q = require('q');
ClassOne.prototype.myMethod = function(myClass) {
myClass.test().then(function(result) { // now test returns a promise
console.log('returns '+ result); // that we call .then() on
});
};
ClassTwo.prototype.test = function() {
var one = function() {
var deferred = Q.defer();
console.log('ONE');
process.nextTick(function() { deferred.resolve()});
return deferred.promise;
};
var two = function() {
var deferred = Q.defer();
console.log('TWO');
process.nextTick(function() { deferred.resolve()});
return deferred.promise;
};
var three = function() {
var deferred = Q.defer();
console.log('THREE');
process.nextTick(function() { deferred.resolve('FOUR')});
return deferred.promise;
};
return one()
.then(two)
.then(three)
};
(new ClassOne()).myMethod(new ClassTwo());

Chaining ajax requests with jQuery's deferred

I have a web app which must call the server multiple times. So far, I had a long nested callback chain; but I would like to use jQuery's when,then etc. functionality. However, I can't seem to get stuff running again after using a then.
$
.when ($.get('pages/run-tool.html'))
.then (function (args)
{
// This works fine
alert(args);
$('#content').replaceWith (args);
$('#progress-bar').progressbar ({value: 0});
})
.then ($.get('pages/test.html'))
.done (function(args)
{
// This prints the same as the last call
alert (args);
});
What am I doing wrong? I guess its some scoping issue, as I can see the second get call being executed. Using two different args variables does not help as the argument passed to the done function is still the first get request.
As an update:
With modern jquery (1.8+) you don't need the preliminary when because get returns a Deferred Promise.
Also, pipe is deprecated. Use then instead. Just be sure to return the result of the new get which becomes the Promise attached to by subsequent then/*done*/fail calls.
So:
$.get('pages/run-tool.html')
.then (function (args) { // this will run if the above .get succeeds
// This works fine
alert(args);
$('#content').replaceWith (args);
$('#progress-bar').progressbar ({value: 0});
})
.then (function() { // this will run after the above then-handler (assuming it ran)
return $.get('pages/test.html'); // the return value creates a new Deferred object
})
.done (function(args) { // this will run after the second .get succeeds (assuming it ran)
alert (args);
});
All three callbacks (the two with then and the one with done) are applied to the same request – the original when call. This is because then returns the same Deferred object, rather than a new one, so that you can add multiple event handlers.
You need to use pipe instead.
$
.when ($.get('pages/run-tool.html'))
.then (function (args)
{
// This works fine
alert(args);
$('#content').replaceWith (args);
$('#progress-bar').progressbar ({value: 0});
})
.pipe (function() {
return $.get('pages/test.html'); // the return value creates a new Deferred object
})
.done (function(args)
{
alert (args);
});
Here is an wonderfully simple and highly effective AJAX chaining / queue plugin. It will execute you ajax methods in sequence one after each other.
It works by accepting an array of methods and then executing them in sequence. It wont execute the next method whilst waiting for a response.
//--- THIS PART IS YOUR CODE -----------------------
$(document).ready(function () {
var AjaxQ = [];
AjaxQ[0] = function () { AjaxMethod1(); }
AjaxQ[1] = function () { AjaxMethod2(); }
AjaxQ[3] = function () { AjaxMethod3(); }
//Execute methods in sequence
$(document).sc_ExecuteAjaxQ({ fx: AjaxQ });
});
//--- THIS PART IS THE AJAX PLUGIN -------------------
$.fn.sc_ExecuteAjaxQ = function (options) {
//? Executes a series of AJAX methods in dequence
var options = $.extend({
fx: [] //function1 () { }, function2 () { }, function3 () { }
}, options);
if (options.fx.length > 0) {
var i = 0;
$(this).unbind('ajaxComplete');
$(this).ajaxComplete(function () {
i++;
if (i < options.fx.length && (typeof options.fx[i] == "function")) { options.fx[i](); }
else { $(this).unbind('ajaxComplete'); }
});
//Execute first item in queue
if (typeof options.fx[i] == "function") { options.fx[i](); }
else { $(this).unbind('ajaxComplete'); }
}
}
The answer cdr gave, which has the highest vote at the moment, is not right.
When you have functions a, b, c each returns a $.Deferred() object, and chains the functions like the following:
a().then(b).then(c)
Both b and c will run once the promise returned from a is resolved. Since both then() functions are tied to the promise of a, this works similiar to other Jquery chaining such as:
$('#id').html("<div>hello</div>").css({display:"block"})
where both html() and css() function are called on the object returned from $('#id');
So to make a, b, c run after the promise returned from the previous function is resolved, you need to do this:
a().then(function(){
b().then(c)
});
Here the call of function c is tied to the promise returned from function b.
You can test this using the following code:
function a() {
var promise = $.Deferred();
setTimeout(function() {
promise.resolve();
console.log("a");
}, 1000);
return promise;
}
function b() {
console.log("running b");
var promise = $.Deferred();
setTimeout(function () {
promise.resolve();
console.log("b");
}, 500);
return promise;
}
function c() {
console.log("running c");
var promise = $.Deferred();
setTimeout(function () {
promise.resolve();
console.log("c");
}, 1500);
return promise;
}
a().then(b).then(c);
a().then(function(){
b().then(c)
});
Change the promise in function b() from resolve() to reject() and you will see the difference.
<script type="text/javascript">
var promise1 = function () {
return new
$.Deferred(function (def) {
setTimeout(function () {
console.log("1");
def.resolve();
}, 3000);
}).promise();
};
var promise2 = function () {
return new
$.Deferred(function (def) {
setTimeout(function () {
console.log("2");
def.resolve();
}, 2000);
}).promise();
};
var promise3 = function () {
return new
$.Deferred(function (def) {
setTimeout(function () {
console.log("3");
def.resolve();
}, 1000);
}).promise();
};
var firstCall = function () {
console.log("firstCall");
$.when(promise1())
.then(function () { secondCall(); });
};
var secondCall = function () {
console.log("secondCall")
$.when(promise2()).then(function () { thirdCall(); });
};
var thirdCall = function () {
console.log("thirdCall")
$.when(promise3()).then(function () { console.log("done"); });
};
$(document).ready(function () {
firstCall();
});
</script>
I thought I would leave this little exercise here for anyone who may find it useful, we build an array of requests and when they are completed, we can fire a callback function:
var urls = [{
url: 'url1',
data: 'foo'
}, {
url: 'url2',
data: 'foo'
}, {
url: 'url3',
data: 'foo'
}, {
url: 'url4',
data: 'foo'
}];
var requests = [];
var callback = function (result) {
console.log('done!');
};
var ajaxFunction = function () {
for (var request, i = -1; request = urls[++i];) {
requests.push($.ajax({
url: request.url,
success: function (response) {
console.log('success', response);
}
}));
}
};
// using $.when.apply() we can execute a function when all the requests
// in the array have completed
$.when.apply(new ajaxFunction(), requests).done(function (result) {
callback(result)
});
My way is to apply callback function:
A(function(){
B(function(){
C()})});
where A, B can be written as
function A(callback)
$.ajax{
...
success: function(result){
...
if (callback) callback();
}
}

Categories