I have an issue with js promises that I hope someone can help me with. The function myFunction below performs a $.ajax call and therefore returns a promise. As I need to hand control back to the browser in order to show the refreshed div that this function updates as I recurse, I'm calling a setTimeout as follows:
var nextBitOfWork = function () {
return myFunction(email);
};
setTimeout(nextBitOfWork, 0);
where myFunction (which recurses) now returns a promise when it's done doing it's $.ajax call.
If I simply call:
return myFunction(email);
without the setTimeout function construct above, the promise is passed through and all my promises are captured and allow me to get the array output I need and everything works great when recursion ends. But without the setTimeout I don't get the browser refresh. Using it as above I get the div update refresh displaying, but seem to lose the promise and so the script continues and I don't get to fill the array that myFunction builds as it recurses.
Any thoughts on how to make sure the setTimeout passes on the promise reliably so that I build the response array and display the div updates as I do so?
Thanks, in advance, for your help!
OK - now have the following:
var func = function () {
myFunction(email);
};
return refreshscreen(func,0);
where refreshscreen is:
function refreshscreen(func,time) {
var timer = $.Deferred();
setTimeout(function () {
return func().then(timer.resolve());
}, time);
return timer.promise();
}
Still the same issue - although the browser renders the div, the array I build with myFunction for the collected $.ajax responses is only 1 element in length - though it recurses 20 times! Without the call to refresh screen, the array builds fine, but the browser never renders the div's as we recurse!
setTimeout does not return the value returned by the function that you pass in. (It returns a value you can use to stop the timeout by passing it in to clearTimeout)
So in order to receive the returned value from myFunction just wrap it in a function in your call to setTimeout.
setTimeout(function () {
var promise = myFunction(email);
// do something with promise...
}, 0);
Related
I'm having an issue with this piece of code:
function aFunction(){
....
var deferred = $q.defer();
debounce(function () {
deferred.resolve(service.subscribe0(data));
}, 350);
return deferred.promise;
}
The returned promise is never resolved. Debounce function is a 3rd party function with a lot of downloads from NPM, so I can be sure it works.
Can it be because the return statement "removes" the scope of the function? How can I avoid this and resolve the promise?
You misunderstand what debounce() does.
debounce() is a function that accepts a function, and returns a function. The returned function will only call the passed callback after N milliseconds of silence (that is, if you call the debounced function very quickly in sequence, only the last call will take effect, after the time elapses).
debounce() itself doesn't call the function you pass it. So, deferred.resolve() never gets called.
I would expect something like:
const getData = data => Promise.resolve( service.subscribe0( data ));
grid.addEventListener( 'scroll', debounce( getData, 350 ));
We want the grid to update itsself on scroll, but debounce it so it won't flood the service with calls. So we have to debounce the function tied to the scrolling instead of the data call, since there's no link between two different data calls.
I have the following code:
function doSomething() {
//xhr here
setTimeout(function() {
var value = 42;
}, 10);
return {
then: function(callback) {
callback(value);
}
};
}
doSomething().then(function(result) {
log("got a result", result);
});
And can't figure out how to access the value.
I need this to be promise-based solution in order to use in multiple places
JSFidle link
Update:
We are not using any libraries in that projects
There are a couple of problems there:
value is local to the function you're passing into setTimeout, because that's where you've declared it. You could fix this issue by declaring it in doSomething instead.
The bigger issue is that what you have there isn't a promise, it's just a function that returns an object when you call it that has a then method. Here's the order in which things happen:
You call doSomething
It sets a timer to set a value.
It creates an object with a then function.
It returns the object.
You call the then function immediately.
then tries to access value (which it can't because of the declaration issue, but would be a problem anyway).
Some time later, value is set by the callback when the timer fires.
To be a promise, the then function on object you return would have to store a reference to the callback passed into it, and call the callback later, when value has been set (e.g., the promise has been fulfilled).
Rather than implementing your own promises library, I'd suggest using one of the several that have already been written and debugged.
I'm just pointing out, that in order to "fix" your issue you need to return the then this way:
function doSomething() {
//xhr here
return {
then: function(callback) {
setTimeout(function() {
callback(42); // callback from within the async action
}, 10);
}
};
}
doSomething().then(function(result) {
log("got a result", result);
});
Please read TJ's answer and consider using a promise library - also consider reading this post that explains how promise resolution looks like.
Namely: Your then needs to in turn return a promise when called (rather than just set a timeout) for chaining, and the callback should be assimilated before waiting for it.
I am wanting an image to change to a loading symbol when clicked, and then change back when the AJAX functions are finished.
I've got the below, however it is not changing at all (well, it is changing, but immediately changing back without waiting for the ajax call to finish). Why is this happening? Is this because my ajax call is enclosed in a function?
//adding product to cart
$("body").on("click", "#cart_product_add", function() {
var clickedIcon = $(this);
//show the spinning loader
var loader = $(clickedIcon).attr("src").replace("add_icon.png", "green_loading.gif");
$(clickedIcon).attr("src", loader);
var code = $(clickedIcon).data("product_code");
var description = $(clickedIcon).data("product_description");
var whqc = $(clickedIcon).data("product_whqc");
var qty = $(clickedIcon).prev("input[name=qty]").val();
$.when(
addCartProduct($("input[name=cart_id]").val(), code, description, whqc, qty)
).then(function() {
loader = $(clickedIcon).attr("src").replace("green_loading.gif", "add_icon.png");
$(clickedIcon).attr("src", loader);
});
});
$.when() can only do its job if you pass it one or more promises. It doesn't have any magic ability to know when an ajax call inside the function you pass it is done. So, if you don't pass it a promise, it just executes then .then() handler immediately and doesn't wait for anything (which is what you're seeing).
Since jQuery Ajax calls already return a promise, you probably can just return that promise. And, if you only have one operation you're waiting for, there's no reason to use $.when either. Here's a skeleton for how you could do it:
addCartProduct($("input[name=cart_id]").val(), code, description, whqc, qty).then(function() {
loader = $(clickedIcon).attr("src").replace("green_loading.gif", "add_icon.png");
$(clickedIcon).attr("src", loader);
});
function addCartProduct() {
return $.ajax(...);
}
This could help you
http://api.jquery.com/ajaxstart/
you can specify this once and it triggers for every ajax request.
currently I am reading these two answers to better understand the problem and get better at javascript:
wait for async task to finish
Promises in AngularJS and where to use them?
But at the moment I have this code:
function getRegistration(id) {
var numRegistered = 0;
api.search({num: id, state: "done"})
.then(function(response) {
numRegistered = response.data.content.length;
)};
console.log(numRegistered);
}
now I can expect numRegistered to be 0 because it probably executes that statement before the asynchronus call has finished. I am finding it hard to understand how to do this so that I wait for the call, assign the value and return it...the solutions appear to be use a call back function or use a promise. Could someone help me (yes I come from an object oriented background...).
api.search basically executes an $http.get.
Here is the promise approach:
function getRegistration(id) {
return api.search({num: id, state: "done"}); // we just return the promise
}
And then, in your controller or a service, you'd wait for it to resolve:
getRegistration(id).then(function(res) {
var numRegistered = res;
// rest of the code here
});
But again, although now your function returns something (a promise), you still need to wait for the promise to be resolved before having numRegistered available.
This is very similar to what happens inside your original .then callback, but here we've moved the non-getRegistration code outside of the getRegistration function, assuming that getRegistration is inside some service that shouldn't know about the rest of your code.
I have a situation where my WinJS app wants to call a function which may or may not be async (e.g. in one situation I need to load some data from a file (async) but at other times I can load from a cache syncronously).
Having a look through the docs I though I could wrap the conditional logic in a promise like:
A)
return new WinJS.Promise(function() { // mystuff });
or possibly use 'as' like this:
B)
return WinJS.Promise.as(function() { // mystuff });
The problem is that when I call this function, which I'm doing from the ready() function of my first page like this:
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function () {
Data.Survey.init().done(function (result) {
// do some stuff with 'result'
});
}
});
When it is written like 'A' it never hits my done() call.
Or if I call it when it's written like 'B', it executes the code inside my done() instantly, before the promise is resolved. It also looks from the value of result, that it has just been set to the content of my init() function, rather than being wrapped up in a promise.
It feels like I'm doing something quite basically wrong here, but I'm unsure where to start looking.
If it's any help, this is a slimmed down version of my init() function:
function init() {
return new WinJS.Promise(function() {
if (app.context.isFirstRun) {
app.surveyController.initialiseSurveysAsync().then(function (result) {
return new WinJS.Binding.List(result.surveys);
});
} else {
var data = app.surveyController.getSurveys();
return new WinJS.Binding.List(data);
}
});
}
Does anyone have any thoughts on this one? I don't believe the 'may or may not be async' is the issue here, I believe the promise setup isn't doing what I'd expect. Can anyone see anything obviously wrong here? Any feedback greatly appreciated.
Generally speaking, if you're doing file I/O in your full init routine, those APIs return promises themselves, in which case you want to return one of those promises or a promise from one of the .then methods.
WinJS.Promise.as, on the other hand, is meant to wrap a value in a promise. But let me explain more fully.
First, read the documentation for the WinJS.Promise constructor carefully. Like many others, you're mistakenly assuming that you just wrap a piece of code in the promise and voila! it is async. This is not the case. The function that you pass to the constructor is an initializer that receives three arguments: a completeDispatcher function, an errorDispatcher function, and a progressDispatcher function, as I like to call them.
For the promise to ever complete with success, complete with an error, or report progress, it is necessary for the rest of the code in the initializer to eventually call one of the dispatchers. These dispatchers, inside the promise, then loop through and call any complete/error/progress methods that have been given to that promise's then or done methods. Therefore, if you don't call a dispatcher at all, there is no completion, and this is exactly the behavior you're seeing.
Using WinJS.Promise.as is similar in that it wraps a value inside a promise. In your case, if you pass a function to WinJS.promise.as, what you'll get is a promise that's fulfilled with that function value as a result. You do not get async execution of the function.
To achieve async behavior you must either use setTimeout/setInterval (or the WinJS scheduler in Windows 8.1) to do async work on the UI thread, or use a web worker for a background thread and tie its completion (via a postMessage) into a promise.
Here's a complete example of creating a promise using the constructor, handling complete, error, and progress cases (as well as cancellation):
function calculateIntegerSum(max, step) {
if (max < 1 || step < 1) {
var err = new WinJS.ErrorFromName("calculateIntegerSum", "max and step must be 1 or greater");
return WinJS.Promise.wrapError(err);
}
var _cancel = false;
//The WinJS.Promise constructor's argument is a function that receives
//dispatchers for completed, error, and progress cases.
return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
var sum = 0;
function iterate(args) {
for (var i = args.start; i < args.end; i++) {
sum += i;
};
//If for some reason there was an error, create the error with WinJS.ErrorFromName
//and pass to errorDispatch
if (false /* replace with any necessary error check -- we don’t have any here */) {
errorDispatch(new WinJS.ErrorFromName("calculateIntegerSum", "error occurred"));
}
if (i >= max) {
//Complete--dispatch results to completed handlers
completeDispatch(sum);
} else {
//Dispatch intermediate results to progress handlers
progressDispatch(sum);
//Interrupt the operation if canceled
if (!_cancel) {
setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
}
}
}
setImmediate(iterate, { start: 0, end: Math.min(step, max) });
},
//Cancellation function
function () {
_cancel = true;
});
}
This comes from Appendix A ("Demystifying Promises") of my free ebook, Programming Windows Store Apps in HTML, CSS, and JavaScript, Second Edition (in preview), see http://aka.ms/BrockschmidtBook2.
You would, in your case, put your data initialization code in the place of the iterate function, and perhaps call it from within a setImmediate. I encourage you to also look at the WinJS scheduler API that would let you set the priority for the work on the UI thread.
In short, it's essential to understand that new WinJS.Promise and WinJS.Promise.as do not in themselves create async behavior, as promises themselves are just a calling convention around "results to be delivered later" that has nothing inherently to do with async.