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() { ... });
Related
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 an async function like:
$scope.get_device_version = function(){
return $q(function(resolve, reject){
$http.get('/api/get_version')
.then(function(response) {
resolve(response.data.version);
},function(response) {
reject("Lost connection..");
});
});
};
Question 1: Now I want to run that function 10 times in a row sequentially, what do I do?
Question 2: Now I want to run the function until I get a desired answer from the http-request, what do I do?
Question 1
.then(callback) returns a new promise resolving to the return value of callback, or if the return value is another promise, to the settled value of that promise. This can be used to chain asynchronous calls. For example, given
function myFunction() { return promise; }
the following snippet will call myFunction 10 times sequentially
var result = $q.resolve();
for (var i = 0; i < 10; ++i) {
result = result.then(myFunction);
}
result.then(function () { /* myFunction has been called 10 times */ })
Question 2
Here, recursion is required since you don't know the exact number of times you will need to iterate. For example, given myFunction as above,
function wrapper() {
var deferred = $q.defer();
iter();
function iter(res) {
// skip first iter() call, wait for acceptable res
if (result !== void 0 && /* res is what I want */) {
return deferred.resolve(res);
}
// recurse
return myFunction().then(iter);
}
return deferred.promise;
}
wrapper().then(function (res) { /* res is an acceptable response */ })
Note that for this use case, promises do not really offer an advantage over simple callbacks.
Question 1:
var request = null;
for( var i = 0; i < 10; i++ ) {
if( request )
request.then( $scope.get_device_version );
else
request = $scope.get_device_version();
}
Question 2:
$scope.get_device_version = function(){
return $q(function(resolve, reject){
$http.get('/api/get_version')
.then(function(response) {
if( /*Not a desired answer? run again:*/ )
$scope.get_device_version()
else
resolve(response.data.version);
},function(response) {
reject("Lost connection..");
});
});
};
This is pseudo code, i have not tested it.
var checkDeviceVersion=function (times) {
$scope.get_device_version().then(function(data) {
if(times==0) return;
checkDeviceVersion(times-1);
});
}
var keepChecking=function () {
$scope.get_device_version().then(function(data) {
if(data.condition) return;
keepChecking();
});
}
Invoke then as checkDeviceVersion(10) or keepChecking()
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.
So i'm using angularJS and$q service. But for simplicity i'm using $timeout since it creates a promise.
Question:
Is it possible to only return a promise when a conditional has been satisfied? For this example, I want to wait for carousel.params.caruselReady to return true before I move to next .then.
$timeout(function(){
if(Carousel.params.ready()){
return ready;
}
},0).then(function(ready){
//...do stuff..//
}
Carousel.params.ready is coming from Carousel.js a factory:
//this function gets called everytime a image has been loaded. when all images are rendered than carousel is ready
carouselElLoaded: function (result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
if (Carousel.params.pageRenderedLength >= Carousel.params.pageLength) {
Carousel.params.carouselReady = true;
}
},
Lastly, carouselElLoad is being called from wa.pages.js (a directive)
$img.onload = function (e) {
var self = this;
$timeout(function () {
return self;
}, 0).then(function () {
Carousel.set.carouselElLoaded(e);
});
};
Currently, I'm using a $watch to achieve this but I was wondering if I could accomplish the same w/o a watcher.
You can use a promise instead of a boolean flag and it will do exactly what you want.
In Carousel.js define a promise names isCarouselReady and resolve it once the carousel is ready, your code should like something like this:
// During the initialisation of your factory
var carouselDeferred = $q.defer()
Carousel.params.isCarouselReady = carouselDeferred.promise;
//this function gets called everytime a image has been loaded. when all images are rendered than carousel is ready
carouselElLoaded: function (result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
if (Carousel.params.pageRenderedLength >= Carousel.params.pageLength) {
carouselDeferred.resolve(/* What ever you'd like here */);
}
},
Now you all you have to do in order to use it is:
Carousel.params.isCarouselReady.then(function() {
// Your logic
});
Your last part would be nicer if it would look something like this:
$img.onload = function (e) {
$scope.$apply(function(){
Carousel.set.carouselElLoaded(e);
});
};
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.