How to use jQuery's deferred object with custom javascript objects? - javascript

I have a standard javascript object whose prototype is extended with a .start() method taking 2 callbacks as arguments: success and failure respectively. This method performs some asynchronous processing (it's not AJAX) and depending on the result of this processing it invokes either the success or the failure callbacks.
Here's how this could be schematized:
function MyObject() {
}
MyObject.prototype.start = function(successCallback, errorCallback) {
(function(s, e) {
window.setTimeout(function() {
if (Math.random() < 0.8) {
s();
} else {
e();
}
}, 2000);
})(successCallback, errorCallback);
}
It's not really important the exact processing performed inside the method, only that it is asynchronous and non-blocking. I have no control over the point of time when the start method will finish the processing. I also have no control over the prototype and implementation of this method.
What I have control over is the success and failure callbacks. It is up to me to provide them.
Now I have an array of those objects:
var arr = [ new MyObject(), new MyObject(), new MyObject() ];
The order of elements in this array is important. I need to trigger the .start() method on each element of the array consecutively but only once the previous has completed (i.e. the success callback was called). And if an error occurs (the failure callback is called) I want to stop the execution and no longer invoke the .start method on the remaining elements of the array.
I could implement this naively by using a recursive function:
function doProcessing(array, index) {
array[index++].start(function() {
console.log('finished processing the ' + index + ' element');
if (index < array.length) {
doProcessing(array, index);
}
}, function() {
console.log('some error ocurred');
});
}
doProcessing(arr, 0);
This works fine but looking at the jQuery's deferred Object that was introduced in jQuery 1.5 I think that there is a room for improvement of this code. Unfortunately I don't feel very comfortable yet with it and I am trying to learn it.
So my question is is it possible to adapt my naive code and take advantage of this new API and if yes, could you provide me with some pointers?
Here's a jsfiddle with my implementation.

You could do something like this: (jsFiddle)
function MyObject() {
}
MyObject.prototype.start = function(queue) {
var deferred = $.Deferred();
//only execute this when everything else in the queue has finished and succeeded
$.when.apply(jQuery,queue).done(function() {
window.setTimeout(function() {
if (Math.random() < 0.8) {
deferred.resolve();
} else {
deferred.reject();
}
}, 2000);
});
return deferred;
}
var arr = [ new MyObject(), new MyObject(), new MyObject() ];
var queue = new Array();
$.each(arr, function(index, value) {
queue.push(value.start(queue)
.done(function() {
console.log('succeeded ' + index);
})
.fail(function() {
console.log('failed ' + index);
}));
});
Not quite sure wether you would consider this an improvement, though.

When we program, to remember the GRASP principles or guidelines is very important.
http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)
To get High Cohesion and Low Coupling means that our code will be better, more reusable and easier to maintain.
So, the class MyObject mustn't known the queue existance. MyObject will know its own features and methods and anything more.
// Class MyObject
function MyObject(name) {
this.name = name;
}
MyObject.prototype.start = function() {
var deferred = $.Deferred();
var self = this;
setTimeout(function() {
if (Math.random() <= 0.8) {
console.log(self.name + "... ok");
deferred.resolve();
} else {
console.log(self.name + "... fail");
deferred.reject();
}
}, 1000);
return deferred.promise();
}
The main/caller function will know MyObject existance and it will create three instances that they will be executed sequentially.
// Create array of instances
var objectArray = [ new MyObject("A"), new MyObject("B"), new MyObject("C") ];
// Create array of functions to call start function
var functionArray = [];
$.each(objectArray, function(i, obj) {
functionArray.push(
function() {
return obj.start();
}
);
});
// Chain three start calls
$.iterativeWhen(functionArray[0], functionArray[1], functionArray[2])
.done(function() {
console.log("First: Global success");
// Chain three start calls using array
$.iterativeWhen.apply($, functionArray)
.done(function() {
console.log("Second: Global success");
})
.fail(function() {
console.log("Second: Global fail");
});
})
.fail(function() {
console.log("First: Global fail");
});
I have built a plugin for jQuery: iterativeWhen. It works with jQuery 1.8 and later versions.
$.iterativeWhen = function () {
var deferred = $.Deferred();
var promise = deferred.promise();
$.each(arguments, function(i, obj) {
promise = promise.then(function() {
return obj();
});
});
deferred.resolve();
return promise;
};
Jsfiddle here: http://jsfiddle.net/WMBfv/

There's nothing wrong with your implementation. And as we all know, using jQuery isn't always the best method.
I'd do it like this: (without the need to modify the MyObject class..)
function doProcessing(array, index) {
var defer = new $.Deferred();
$.when(defer).then(doProcessing);
array[index++].start(function() {
log('finished processing the ' + index + ' element');
if (index < array.length) {
defer.resolve(array, index);
}
}, function() {
log('some error ocurred => interrupting the process');
});
};
As you can see, there's no real advantage over the plain JavaScript method. :)
Here's my fiddle: http://jsfiddle.net/jwa91/EbWDQ/

Related

cannot order js function execution using promises

Basically, i have to 2 functions A and B to get data from rest, then I want to run function C after successfully get the data
this is my code :
JS:
var A = function() {
$.getJSON('linkA', function(data) {
a = data.total;
console.log("A : " + a);
});
return new Promise(function(resolve, reject) {
resolve('Got a');
});
};
var B = function() {
$.getJSON('linkB', function(data) {
b = data.total;
console.log("B:" + b);
});
return new Promise(function(resolve, reject) {
resolve('Got b');
});
};
function run() {
Promise.all([A(), B()]).then(function() {
console.log("Got A and B")
});
}
HTML:
<script>
run();
</script>
I want the result should be in Console :
A: //data
B: //data
Got A and B
However , i still got "Got A and B" before the other two lines . I guess because getting data takes quite a long time so the program write the "Got A and B" first. But there is must be someway to get the goal right ??
You're immediately resolving the promises, rather than waiting until you get the data back.
You're also falling prey to the promise creation anti-pattern. $.getJSON already gives you a thenable*, so just use it and use then to transform the data (by accessing total):
var A = function() {
return $.getJSON('linkA').then(function(data) {
return data.total;
});
};
And the same with B. Then your Promise.all code will work, and if you tweak it you can actually get a and b:
function run() {
Promise.all([A(), B()]).then(function(results) {
var a = results[0], b = results[1];
// ...use `a` and `b`...
});
}
Or if you can rely on ES2015+ features, you can use an arrow function and destructuring (or a normal function and destructuring if you prefer):
function run() {
Promise.all([A(), B()]).then(([a, b]) => {
// ...use `a` and `b`...
});
}
Since the rest of your code just relies on jQuery, not Promise support in the browser, you could also use jQuery's $.when:
function run() {
$.when(A(), B()).then(function(a, b) {
// ...use `a` and `b`...
});
}
If you needed to explicitly do new Promise (you don't here) to interface with a non-thenable API, it would look like this:
// YOU DON'T NEED TO DO THIS WITH $.getJSON, but with non-thenable APIs you might
var A = function() {
return new Promise(function(resolve, reject) {
$.getJSON('linkA', function(data) {
resolve(data.total);
}).fail(function(jqXHR, textStatus, errorThrown) {
reject(errorThrown || new Error(textStatus));
});
});
};
* "thenable" - A promise-like thing, see the Promises/A+ spec.
var url = 'https://jsonplaceholder.typicode.com/posts/';
Promise.all( [url + 1, url + 2 ].map( $.get ) )
.then(
console.log
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How can I redirect a promise to an existing deferred?

I have a function which returns a promise. I create a jQuery deferred for this purpose, which might be resolved/rejected in custom ways, depending on implementation.
One implementation uses an AJAX call, and there I'd like to redirect or queue the failure/resolution of the AJAX promise to the one which was created earlier. This means that whenever the AJAX call has a resolve/reject/progress, the deferred should trigger its own resolve/reject/progress too with the same arguments.
Here is some dummy sample code.
function Test() {
}
Test.prototype.doSomething() {
this._deferred = $.Deferred();
this.doSomethingImpl();
return this._deferred;
}
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
// resolve/reject/progress here the this._deferred based on the ajax promise
}
I know I can do it in a verbose way using the AJAX done, fail and progress callbacks, and manually call my deferred's corresponding method (resolve, reject or progress), but I'm seeking for kind of a one-liner, if there is any.
EDIT
Here is a code which is similar to the real one, using knockoutjs.
function GridViewModel() {
var self = this;
self.pageIndex = ko.observable(0);
...
self._refreshRequest = ko.observable(null).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });
self._doRefresh = function() {
$.ajax(...)
.done(result) { // update rows, etc. }
.then(
function(r) { self._refreshPromise.resolve(r); },
function(r) { self._refreshPromise.reject(r); },
function(r) { self._refreshPromise.progress(r); }
)
.always(function() { self._refreshPromise = null; }
// here I used the obvious verbose redirecting
}
...
ko.computed(function() {
var pageIndex = self.pageIndex();
if (ko.computedContext.isInitial()) return;
this.refreshRequest("Paging");
});
ko.computed(function() {
var refreshRequest = self.refreshRequest();
if (ko.computedContext.isInitial() || !refreshRequest) return;
self._doRefresh(refreshRequest);
}
}
GridViewModel.prototype.Refresh = function(type) {
this._refreshPromise = this._refreshPromise || $.Deferred();
this._refreshRequest(type);
return this._refreshPromise;
}
This code is a snippet of a complex data grid viewmodel class, and the fancy refresh solution is there to ensure that refreshing is throttled.
Yes, it would be possible to redirect the resolution (in a perfect world1, just deferred.resolve(promise)), but it's completely unnecessary. Don't create deferreds when you're already calling something that produces a promise for you - avoid the deferred antipattern! You can simply return that very promise:
Test.prototype.doSomething = function() {
return this.doSomethingImpl();
};
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
return ajax; // the promise
};
1) where jQuery follows the Promises/A+ specification and deferred.resolve accepts thenables

How can I fire JQuery functions sequentially with deferred()?

I am really having trouble wrapping my head around the deferred() method inside jquery. I've spent several hours reading the documentation, but I still don't fully understand what I'm doing.
My basic problem is, I have a series of functions (not ajax calls) that I want to run in sequence, but stop all processes if there is an error in any of them.
Here is how far I've gotten (I've stripped out a bunch of unneeded code and just left the basic idea)
//The module var myModule = (function() {
//Defaults
var vOne;
var VTwo;
var deferred = $.Deferred();
//Private method
var _myPrivateFunction1 = function(userID) {
if(userID >= 10) {
//All is good, set var vOne to true and run next function
vOne = true;
return deferred.promise();
} else {
//User is not allowed, stop everything else and show the error message
return deferred.reject();
}
}
var _myPrivateFunction2 = function() {
if(vOne === true) {
//Ok we can keep going
return deferred.promise();
} else {
//again stop everything and throw error
return deferred.reject();
}
};
var _myPrivateFunction3 = function(element) {
//...and so on
};
var _errorMsgFunction = function(msg) {
$.log("There was an error: " + msg);
return false;
};
//Public method
var myPublicFunction = function(element,call) {
//element is jquery object passed through user "click" event
var userID = element.data('id')
var userAction = element.data('action');
//Now... i want to fire _myPrivateFunction1, _myPrivateFunction2, _myPrivateFunction3 in sequence and STOP all processes, and run
// _errorMsgFunction if there is an error in any of them.
//This is how far I've gotten...
_myPrivateFunction1(userID).then(_myPrivateFunction2(userAction), _errorMsgFunction("Error in _myPrivateFunction2")).then(_myPrivateFunction3(element),_errorMsgFunction("Error in _myPrivateFunction3")).fail(_errorMsgFunction("Error in _myPrivateFunction1"));
};
// Public API
return {
myPublicFunction: myPublicFunction
};
})();
So right now I keep getting "Error in _myPrivateFunction2" (I'm forcing this error for testing purposes), but the other functions after continue to fire...They don't stop. What am I missing here?
You cannot share deferred objects. You should create a different promise from a deferred for each function.
Here is some very simple example, using sycnhronus functions for the sake of simplicity, although promises are meant to be used with asynchronous functions:
var func1 = function(arg){
var dfd = jQuery.Deferred();
if (arg === 0) {
dfd.resolve('func1 Ok');
} else {
dfd.reject('func1 arg != 0');
}
return dfd.promise();
}
var func2 = function(arg){
var dfd = jQuery.Deferred();
if (arg === 0) {
dfd.resolve('func2 Ok');
} else {
dfd.reject('func2 arg != 0');
}
return dfd.promise();
}
var func3 = function(arg){
var dfd = jQuery.Deferred();
if (arg === 0) {
dfd.resolve('func3 Ok');
} else {
dfd.reject('func3 arg != 0');
}
return dfd.promise();
}
If the functions does not depend on other to do their processing, we can do it in parallel using jQuery.when
// Parallel processing
jQuery.when(func1(1), func2(0), func3(0)).then(function(res1, res2, res3){
console.log(res1, res2, res3);
}).fail(function(reason){
console.error(reason); // will fail with reason func1 arg != 0
});
If it is a sequence processing (as I undertood your problem is), you should do:
// Sequential processing
func1(0).then(function(res1){
return func2(res1);
}).then(function(res2){
return func3(res2);
}).then(function(res3){
// everything ran ok, so do what you have to do...
}).fail(function(reason){
console.log(reason);
});
The code above will fail with reason:
> func2 arg != 0
If you have mixed parallel and sequential processing to do, then you should mix both approaches.
Disclaimer
As in my example, if func1 or func2 have side effects, they will not be undone within fail() by themselves.
The best practice is to only have side effects when you are absolutely sure that everything went ok, that is, inside the last then() call.
You will need a separate $.deferred() inside each of your functions, because you want to return unique promise for each function.
//Private method
var _myPrivateFunction1 = function(userID) {
var deferred = $.Deferred();
if(userID >= 10) {
//All is good, set var vOne to true and run next function
vOne = true;
deferred.resolve();
} else {
//User is not allowed, stop everything else and show the error message
deferred.reject();
}
return deferred.promise();
}
Then your public function should work.

Best Practices to wait for multiple calls

I have this code as a starting point.
// $ = jQuery
// groupAdata and groupBdata are arrays
function funcA(elem) {
for (f = 0; f < groupAdata.length ; f++) {
// this is an example on how this function calls other functions asynchronously.
elem.children('.partyA').each( function() {
this.innerHTML = "been here" + groupAdata[f];
});
}
}
function funcB(elem) {
// another function that fires more calls
for (f = 0; f < groupAdata.length ; f++) {
$.post(url, somedata, function(data) {
elem.children('.partyB').each( function() {
this.innerHTML = "will be there" + groupBdata[f] + data;
});
}
}
}
$(document).ready(function() {
$('.groupA').each(function () {
funcA(this);
});
$('.groupB').each(function (){
funcB(this);
});
});
function endofitall() {
// call this after all instances of funcA and funcB are done.
}
When running endofitall(), I'd like to be sure that all calls of funcA and funcB are done.
I take that Promises and jQuery.Deferred() would be a good/preferred approach but was not able to map the answers I found to this specific scenario. (It is part of a templating tool that fires multiple dom manipulators func[AB] for multiple DOM elements.)
You can use $.when().
Your goal should be to get to:
// call funcA, call funcB
$.when( funcA(), funcB() )
// when everything is done go on with the callback
.done(endofitall);
In the case of funcA (synchronous function there's no problem and it will work as is).
In the case of funcB (asynchronous) there are some things to consider. If it would be just one ajax call your code should be something like:
// This function returns a promise.
// When it's fulfilled the callback (in your case '.done(endofitall)')
// will be called.
function funcB(somedata){
return $.post(url, somedata);
}
As you are actually making more requests you have to return a resolved promise only when all calls have been fulfilled.
// an *Asynchronous* function, returns an array of promises
function funcB(elem, groupAdata) {
var allCalls = [];
// for each element in the array call the relative async
// function. While you're calling it push it to the array.
groupAdata.forEach(data, function(data){
allCalls.push( $.post(url, data) );
});
// allCalls is now an array of promises.
// why .apply(undefined)? read here: https://stackoverflow.com/a/14352218/1446845
return $.when.apply(undefined, allCalls);
}
At this point you can go for a flat and clear:
$.when( funcA(), funcB() ).done(endofitall);
As a rule of thumb: if you are making async requests try to always return a promise from them, this will help flatten out your code (will post some link later on if you want) and to leverage the power of callbacks.
The above code can surely be refactored further (also, I haven't used a lot of jQuery in the last few years, but the concept applies to any Js library or even when using no library at all) but I hope it will help as a starting point.
References:
$.when
A similar answer here on SO
Call endofitall() inside each iteration for funcA and funcB. Keep a counter and perform the actual work once the counter reaches the number signifying all the tasks are complete.
function funcA(elem) {
for (f = 0; f < groupAdata.length ; f++) {
// these calls are not async
elem.children('.partyA').each( function() {
this.innerHTML = "been here" + groupAdata[f];
});
endofitall();
}
}
function funcB(elem) {
// another function that fires more calls
for (f = 0; f < groupBdata.length ; f++) {
$.post(url, somedata, function(data) {
elem.children('.partyB').each( function() {
this.innerHTML = "will be there" + groupBdata[f] + data;
});
endofitall();
}
}
}
$(document).ready(function() {
$('.groupA').each(function () {
funcA(this);
});
$('.groupB').each(function (){
funcB(this);
});
});
var counter=0;
function endofitall() {
if(++counter==groupAdata.length + groupBdata.length){
//do stuff
}

Chaining more than two tasks using deferred and can this be use for loops?

I am getting used to $.Deferred just now, and it happens that I needed to chain three tasks using $.Deferred - then.
I created a function:
function processA(param1, param2) {
log = $.post('http://domain.com/process',
{
id: param1,
another: param2
}
),
set = log.then(function(html){
if (someCondition) {
console.log('Successful');
// i want to do another ajax call here
// and chain another, that does an ajax again
}
})
}
How do I do that, as stated in the comments of my code.
Is it right? Not tested, just thought while typing this.
set = log.then(function(html){
if (someCondition) {
console.log('Successful');
$.post(....),
def = set.then(function(data){
// i want to do another thing here.
})
}
})
And another thing, is it possible to use the function in a loop?
e.g.
data = [{param1:"...", param2:"..."}, {..., ...}]
$.each(data, function(k,v){
processA(v.param1, v.param2);
})
Here better explained chaining promises:
function authenticate() {
return getUsername()
.then(function (username) {
return getUser(username);
})
// chained because we will not need the user name in the next event
.then(function (user) {
return getPassword()
// nested because we need both user and password next
.then(function (password) {
if (user.passwordHash !== hash(password)) {
throw new Error("Can't authenticate");
}
});
});
}
This is something I wrote for a dice game. Rolling the dice, each dice.roll() gets stored into a $.Deferred object. When all dice have run their animation, you can execute your callbacks or whatever.
var promises = [];
// collect promises
_.each(fDice, function (die) {
promises.push(function () {
return $.Deferred(function (dfd) {
$(die).roll(options, function (data) {
dfd.resolve(data); // resolve the state
});
}).promise(); // send it back
});
});
// roll selected dice
dfrAll(promises).done(function () {
// do something here
});
dfrAll: function (array) {
/// <summary>Resolves n $.Deferred functions from an Array</summary>
/// <param name="Array" type="Array">An Array of $.Deferred functions</param>
/// <returns type="Deferred" />
var dfd = $.Deferred(),
len = array.length,
results = [];
if (len === 0) {
dfd.resolve(results);
} else {
for (var i = 0; i < len; i++) {
var promise = array[i];
$.when(promise()).then(function (value) {
results.push(value);
if (results.length === len) {
dfd.resolve(results);
}
});
}
}
return dfd.promise();
}
For me the storage of deferred functions into an array was the key to resolve my animations. Hope it helps.

Categories

Resources