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.
Related
I am checking whether some elements of my DOM are fully charged and I wanted to do it through a promise function that checks some elements and if not loaded waits for them.
This is my code
var jq = jQuery.noConflict();
var df = jq.Deferred();
function keepTrying() {
try{
var el1 = \\element to search for
var success=true
catch(e){
var success= false
}
if (success) {
//Resolves promises
df.resolve();
return df.promise();
} else {
//Here it retries..
setTimeout(function() {
keepTrying();
}, 500);
}
}
keepTrying();
df.done(function() {
//what to do after
});
Do you think there is an easier way to do it? I am working on Qualtrics so importing external libraries could be tricky when passing from one function to another.
I'd use an inner recursive function, and resolve the promise when the element is found.
You could add a count to that, and reject the promise after n tries etc.
function keepTrying() {
var def = $.Deferred();
(function rec() {
var el = document.getElementById('something');
if ( el === null ) {
setTimeout(rec, 500);
} else {
def.resolve();
}
})();
}
keepTrying().done(function() { ... });
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
I have this function:
waitForFreeAccnt.prototype.isMemberFree = function () {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return true;
} else {
console.log('it is not free');
return false;
}
});
};
I would like to wait till the account is free for up to 10 seconds with something like that:
var test = function () {
for (var start = 1; start < 10; start++) {
var result = self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
But it doesn't work because self.api.getMemberInfo is asynch call. This is super frustrating with Javascript. Any other language it would be so simple to do. How do I force the for loop to wait for self.isMemberFree() to finish executing before proceeding with the loop?
Also to note, this is not in browser execution so I don't care about anything hanging.
When dealing with asynchronous code, you need to make use of callbacks. That is, if you want to do a() and b() in order but a() does something asynchronously, then you need to call b() from within a() once a() has a result. So not:
a(); // does something asynchronously
b(); // tries to use a()'s result but it isn't available yet
... but rather
a(b); // pass b to a() and a() will call it when ready
function a(callback) {
triggerAsyncFunction(function(result) {
if (result === something)
callback("a just finished");
});
}
Note that a() doesn't refer to b() by name, it just calls whatever function is passed in as an argument.
So applying that to your code, maybe something like this:
waitForFreeAccnt.prototype.isMemberFree = function (cbf) {
var self = this;
self.api.getMemberInfo(function () {
cbf(self.api.connect.accountType === 'FREE');
});
};
waitForFreeAccnt.prototype.testMemberXTimes = function(maxAttempts, callback) {
var attempts = 0;
var self = this;
(function attempt() {
self.isMemberFree(function(free) {
if (free)
callback(true);
else if (++attempts < maxAttempts)
setTimeout(attempt, 1000);
else
callback(false);
});
)();
};
this.testMemberXTimes(10, function(isFree) {
// the next part of your code here, or called from here
// because at this point we know we've tested up to
// ten times and isFree tells us the result
});
Note that the way I coded getMemberInfo() it is basically doing the same thing yours was, but instead of returning a boolean it is calling the callback function and passing the same boolean value that you were returning. (I've removed the console.log()s to make the code shorter.)
Note also that you could structure the above to use promises, but the end result will be the same.
You could return a Promise
waitForFreeAccnt.prototype.isMemberFree = function () {
return new Promise((reject, resolve)=>
// set a timeout if api call takes too long
var timeout = setTimeout(()=> reject(Error('API timeout')), 10000);
// make api call
this.api.getMemberInfo(()=> {
clearTimeout(timeout);
resolve(this.api.connect.accountType === 'FREE');
});
);
};
Then use it like this
whatever.isMemberFree().then(isFree=> {
if (isFree)
console.log('it is free');
else
console.log('it is not free');
})
// handle timeout or other errors
.catch(err=> {
console.log(err.message);
});
Building on naomik's answer, if you do it that way you can pretty easily use a for loop with it, using the (most likely) upcoming async/await feature - though it's not part of ES2015.
// Note "async" here! That will make "await" work. It makes the function
// return a promise, which you'll be able to either "await" or
// "test().then" later.
var test = async function () {
for (var start = 1; start < 10; start++) {
// Right here we're using "await" - it makes JavaScript *wait* for
// the promise that comes from self.isMemberFree() to be finished.
// It's really handy because you can use it in loops like "for" and
// "while" without changing the flow of your program!
var result = await self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
For now you'll need to use a transpiler like Babel or Traceur before you can really use async/await, though. It's only supported in Microsoft Edge 14 right now.
And a big emphasis that what is returned from test() isn't whatever you directly return from inside it. If I do this:
var test = async function() { return 15; };
var result = test();
I'm not going to get 15 - I'll get a promise that will resolve as 15:
result.then(function(res) {
console.log(res); // 15
});
// or, use an async function again:
var main = async function() {
console.log(await res); // 15
};
main();
I don't have my work laptop today because it is Sunday, I'm coding this on sublime. Apologise if the syntax is a bit off.
To solve your problem I would recommend changing isMemberFree() to take in a callback function. This is because isMemberFree is async, and you will need a way to report the result after it has done the work.
Then change test function to use setTimeout API to wait a second.
Wrap the function call for isMemberFree() to be in a nested function and call it recursively, that way you'll have synchronize control over the async calls.
Look at the coding example:
waitForFreeAccnt.prototype.isMemberFree = function (done) {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return done(null, true);
} else {
console.log('it is not free');
return done(null, false);
}
});
};
var test = function () {
var testMembership = function(waitAttempt, isFree) {
if (isFree) {
return;
}
else if (waitAttempt > 10) {
// wait exceeded, do something.
return;
}
setTimeout(function() {
self.isMemberFree(function(err, isFree) {
testMembership(waitAttempt+=1, isFree);
});
}, /*total milliseconds in 1sec=*/1000);
}
testMembership(/*WaitAttempts=*/0, /*isFree=*/false);
};
What the above code does is that, presumably something has already been done to the member's account and now test function is called. So it waits for 1 second, then call isMemberFree function, this happens recursively until either isMemberFree() returns true OR the 10 seconds wait has been exceeded.
I have to load two apis. The YouTube api which calls onYouTubePlayerReady when it has loaded and another api for SoundManager which calls soundManager.onready(...) when it has loaded. I do a lot of stuff in each of these ready functions to prepare the site. However, I also need to know when both have completed so I can do more initialization that require both to be fully loaded. Any idea how to have a function that is called when both of these ready functions are called?
Use a callback for both of them.
var callback = (function(){
var count = 0;
return function(){
count++;
if(count === 2){
//both ran and do something
}
}
})();
Then in the end of both onloads just do:
callback();
The things in that if statement will only run on the second time this function i s called.
Small fiddle demo: http://jsfiddle.net/maniator/2X8rF/
Just set a couple flags:
var aDone = false;
var bDone = false;
function whenADone(){
// do your other stuff
aDone = true;
if(bDone) whenBothDone();
}
function whenBDone(){
// do your other stuff
bDone = true;
if(aDone) whenBothDone();
}
There is probably a better way with Defered's, but this is simple and should work. Just keep track of what loaded and what didn't.
var status = {
youtube: false,
sound: false
};
var loaded = function() {
if (!status.youtube) return;
if (!status.sound) return;
// load stuff!
};
var onYoutubePlayerReady = function() {
status.youtube = true;
loaded();
};
soundManager.onready = function() {
status.sound = true;
loaded();
}
Using jQuery deferred, you could build promises for each ready function and combine them with jQuery.when to trigger a final action. For example
function promiseYoutube() {
var dfd = $.Deferred();
window.onYoutubePlayerReady = function() {
console.log("Youtube");
dfd.resolve();
};
return dfd.promise();
}
function promiseSoundManager() {
var dfd = $.Deferred();
window.soundManager.onready = function() {
console.log("SoundManager");
dfd.resolve();
};
return dfd.promise();
}
$.when( promiseYoutube(), promiseSoundManager() ).then(function(){
console.log('Youtube+SoundManager');
});
And a Fiddle simulating these callbacks http://jsfiddle.net/nikoshr/hCznB/
You could have both of your readys set a boolean to true then test against those a $(document).ready(function () { if (youTube === true && soundManager === true) { // Do stuff }});
If using jQuery, take a look at deferred:
http://www.erichynds.com/jquery/using-deferreds-in-jquery/
http://api.jquery.com/category/deferred-object/
Thanks
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/