callback function access to closure variables? - javascript

The following piece of code has a bug but it opened an interesting question. Its using an angular's $http service which returns a promise. Once resolved, the callback function does not have access to the s1 variable.
var ids = [];
ids = ['81074'];
// show what's in
for (var s=0;s<ids.length;s++) {
var s1 = s;
$http.get('http://mydomain.com/api/catalog/product/' + ids[s]).
success(function(data) {
// console.log(s1); <--- reference error
$scope.products[s].setProductData(data);
}).error(function(err){
console.log('--------- error',err);
});
};
s1 gives ReferenceError: s1 is not defined in the debugger
And interestingly, it does have access to the for loop variable s, which is always 1 - as expected, since the promise got resolved after its incremented (hence the bug BTW)
Can someone explains why?
thanks
Lior

This is a classic issue with asynchronous callbacks in a for loop. Both s and s1 are only declared once. Even though you declare var s1; within the loop, JavaScript has no block scope, so it's irrelevant.
This means all iterations share the same variables s and s1, so when the callback completes, $scope.products[s] is looking up the ids.length + 1 product.
Instead, do:
var ids = [];
ids = ['81074'];
function launchRequest(s) {
$http.get('http://example.com/api/catalog/product/' + ids[s]).success(function (data) {
$scope.products[s].setProductData(data);
}).error(function (err) {
console.log('--------- error', err);
});
}
// show what's in
for (var s = 0; s < ids.length; s++) {
launchRequest(s);
}
... which introduces a new function level scope inside launchRequest, which means s is still s inside the function when the callback resolves.

Related

Javascript - How to return object with promise

I'm trying to return an object with a specific structure but i can't return the object.
As you can see in the code if i print inside promise it prints data but if i print outside is empty... why?
How can i return the object parts?
var parts = [];
var part = [];
var articles = [];
part.push(request.query("SELECT u_order, u_familia, u_part, u_type FROM u_part (nolock) where u_order <'92' and u_order <> '100'"));
return Promise.all([Promise.all(part)]).then(function(listOfResults)
{
for(var i=0; i<listOfResults[0][0].length; i++)
{
articles.push(request.query("SELECT st.u_posic, sc.ref, sc.qtt, sc.design FROM st INNER JOIN sc ON st.ref = sc.ref where sc.ststamp ='"+stamp+"' and st.u_posic = '"+listOfResults[0][0][i].u_order+"'"));
//articles.push(request.query("SELECT ststamp, ref, design FROM st (nolock) WHERE u_posic = '"+listOfResults[0][0][i].u_order+"'"));
Promise.all([Promise.all(articles)]).then(function(listOfArticles)
{
for(var j=0; j<listOfResults[0][0].length; j++)
{
parts.push({
u_order: listOfResults[0][0][j].u_order,
u_familia: listOfResults[0][0][j].u_familia,
u_part: listOfResults[0][0][j].u_part,
u_type: listOfResults[0][0][j].u_type,
articles: listOfArticles[0][j-1]
});
console.log(parts); HERE THE OBJECT HAD DATA
}
});
}
console.log(parts); BUT HERE IS EMPTY
}).catch(function(err)
{
console.log(err);
});
Thank you.
The whole point of Promises is being asynchronous. You need to wait for your Promise to resolve or reject to check for data. Specifically, you should only do anything with the returned value of your Promise within the Promise.then() method, which runs as soon as the Promise finishes execution. Changing variables which exist outside the Promise from within it, as you are doing by calling parts.push() is a bad practice. You should return parts within your promise, and then, in a final .then() callback, you should do whatever is required with parts.
Something like:
Promise.all(part).then(makeAllQueries).then(doSomethingWithQueryResults);
You can only be sure you actually have data or not when your final .then callback runs.
Also, doing Promise.all([Promise.all(part)]) does nothing for you, as you're just wrapping your returned array in one more array level.

Why, if `$q.all` is not returned an array of promises, is an exception not thrown?

And on top of this, why are $scope values set BEFORE $q.all is called completely ignored?
Function within main controller:
$scope.apply = function (security) {
var entity = shareDataService.getModalEntity();
var depot = shareDataService.getModalDepot();
$scope.loaded = false;
var myDataPromise;
if (entity == "NULL") {
myDataPromise = getDataService.getDataFromREST(security);
} else {
myDataPromise = $q.all(getDataService.keepICorrect(security));
};
myDataPromise.then(function () {
//DO STUFF
}, function errorCallback(response) {
//DO MORE STUFF
});
}
And my keepICorrect() function in my service (which is a closure):
keepICorrect: function (security) {
var promises = [];
for (var i = 0 ; i < entity.length; i++) {
promises.push(this.getDataFromREST(security, i));
}
return promises;
},
However when the $scope.apply() function is executed, nothing happens. Boolean $scope.loaded does not activate on the scope and no exception is thrown (which is what I was expecting). Why is this?
I've edited the code and made my controller function Apply check if entity = "NULL", and this seems to have solved the issue of whether it is an array or not. Still doesn't answer my question as to why if q.all is not returned an array of promises, does nothing happen in the function it is called in, even if before q.all is called.
Pay attention that the Promise library wraps all errors thrown in enclosed brackets code, so you are really recommended to test your functions before wrapping them in a promise mechanism.
In your case:
keepICorrect: function (security) {
var promises = [];
for (var i = 0 ; i < entity.length; i++) {
promises.push(this.getDataFromREST(security, i));
}
return promises;
},
you say that it is a closure, so I figure that this.getDataFromREST should be undefined. Please try to add correct code progressively in order to do not have your errors hidden and remove the error in your code.

Callback execution sequence in Javascript, Retrieving from IndexeddB

If I've a code like this:
testJSCallbacks();
function testJSCallbacks(){
var i = 0;
for (i = 0; i < 5; i++){
console.log("Step 1 "+i);
foo(i, myCB);
}
}
function foo(key, fnCB){
//retrieve png image blob from indexedDB for the key 'key'. Assume that the database is
//created and started properly
var getRequest = transaction.objectStore("store").get(key);
getRequest.onsuccess = function (event) {
var result = event.target.result;
if(result){
console.log("Step 2 " + key + " Found");
}else{
console.log("Step 2 " + key + " not Found"); //for the same 'key' value this happens before the above result is valid. i.e. key found
}
fnCB(result);
}
}
myCB = function (result){
console.log("Step 3 "+result);
}
Actual Output (just for example):
Step 1 0
Step 1 1
Step 1 2
Step 1 3
Step 1 4
Step 2 0 Found
.
.
Step 3 <result>
.
.
.
Desired output:
Step 1 0
Step 2 0 Found
Step 3 <result value of key 0 goes here>
In my code I'm trying to read png blobs from IndexedDB, which are already stored earlier. But while reading/searching for a specific blob it takes too long to get the result back, meanwhile a search for second blob occurs even though the earlier search is not finished yet.
Can anyone advise what/how would you do if you need to call an asynchronous function in a loop multiple times and the callback takes too long to come? Is my code correct and makes logical sense or this isn't how javascript is done? I'm very new to this and come from an embedded C background.
The problem is the getRequest.onsuccess function is asynchronous, while the for loop executes synchronously. This is why it finishes first... In fact, while you are executing the testJsCallbacks, nothing else will execute until the current execution context ends and control is returned back to the javascript event queue because javascript execution context within the browser is single threaded.
To do what you desire, I would suggest using a promise library. Then you can write code like this (see jsfiddle which uses Q.js library):
testJSCallbacks();
function testJSCallbacks(){
var i = 0,
promise;
for (i = 0; i < 5; i++) {
//Make initial promise if one doesn't exist
if (!promise) {
promise = Q.fcall(getStep(i));
}
//Append to existing promise chain
else {
promise = promise.then(getStep(i));
}
//then function returns another promise that can be used for chaining.
//We are essentially chaining each function together here in the loop.
promise = promise.then(function (key) {
//Log the output of step here
console.log("Step 1 " + key);
return key;
})
//then function takes a callback function with one parammeter (the data).
//foo signature meets this criteria and will use the resolution of the last promise (key).
.then(foo)
//myCB will execute after foo resolves its promise, which it does in the onsuccess callback
.then(myCB);
}
}
function getStep(step) {
return function () {
return step;
}
}
function foo(key) {
//retrieve png image blob from indexedDB for the key 'key'. Assume that the database is
//created and started properly
var getRequest = transaction.objectStore("store").get(key),
//Need to return a promise
deferred = Q.defer();
getRequest.onsuccess = function (event) {
var result = event.target.result;
if(result){
console.log("Step 2 " + key + " Found");
}else{
console.log("Step 2 " + key + " not Found"); //for the same 'key' value this happens before the above result is valid. i.e. key found
}
deferred.resolve(result);
}
return deferred.promise;
}
function myCB (result){
console.log("Step 3: " + result);
}
The jsfiddle uses a setTimeout instead of objectStore to demonstrate the async nature.
Explaining getStep function:
getStep function is like a "seed" function in that it kicks off resolving the chain of what you want to do (i.e. Step 1, Step 2, Step 3). It simply creates a function that returns the value of the variable passed in. This is used to pass into the function that console.logs Step 1 in the promise resolution chain and then returns the value for the next promise resolution (Step 2)... JavaScript has the concept of closures and in order to get the correct value for step number (instead of the value of 'i' at the time when the callbacks are executed) we needed to create a closure for the variable i.
To demonstrate, consider this code:
HTML:
<button type="button">0</button>
<button type="button">1</button>
<button type="button">2</button>
<button type="button">3</button>
<button type="button">4</button>
addHandlers();
function addHandlers() {
//Don't do this. Just demonstrating a feature:
var buttons = document.getElementsByTagName("button") || [],
i, len = buttons.length;
for (var i = 0; i < len; i++) {
buttons[i].onclick = function () {
//will always alert 5
alert(i);
}
}
}
Since the variable i is 5 after the for loop ends, this is the value that is used in the function. This is why you would need to create a closure for i (using getStep again for clarity):
addHandlers();
function addHandlers() {
var buttons = document.getElementsByTagName("button") || [],
i, len = buttons.length;
for (var i = 0; i < len; i++) {
//getStep creates a function with a closure for i at its current value in the loop
buttons[i].onclick = getStep(i);
}
}
function getStep(i) {
return function () {
alert(i);
}
}
Fiddle for before and after.

Asynchronous Request Chaining

I'm looking for advice on extending a previous accepted answer regarding chained ajax requests.
The following asynchronous-chain solution was proposed to sequence 3 ajax requests:
var step_3 = function() {
c.finish();
};
var step_2 = function(c, b) {
ajax(c(b.somedata), step_3);
};
var step_1 = function(b, a) {
ajax(b(a.somedata), step_2);
};
ajax(a, step_1);
This is great for a small pre-determined number of chained ajax functions but does not scale well to the case of a variable number of such functions.
I've tried doing the following but seem to run into scoping issues due my admitted lack of javascript expertise:
var asynch = function (options, fNext) {// do something asynchronously}
var chain = {f:[]} // chain of asynchronous functions
var args = function(n){ //return arguments to feed n'th asynch function }
for (n=0;n<N;n++)
{
var a = args(n);
var ftmp = n==N-1? function(){} : chain.f[n+1]
chain.f[n] = function () {asynch(a, ftmp)}
}
chain.f[0]() // initiate asynchronous chain
What you have is a very common scoping issue with for loops. Each iteration of the for loop is using the same local scope as the parent function, meaning anything that happens asynchronously will end up accessing the last value of the loop rather than the value at the time it was defined. See this fiddle as an example: http://jsfiddle.net/GAG6Q/ Instead of asynch getting called 9 times, it gets called once with a value of 9. You can fix it by simply providing a private scope for the inside of the loop. You'll also want to wrap chain.f[n+1] in a function so that you don't try to assign undefined to ftmp.
http://jsfiddle.net/GAG6Q/1/
var N = 10;
var asynch = function (options, fNext) {
console.log(options);
setTimeout(fNext,500);
}// do something asynchronously}
var chain = {f:[]} // chain of asynchronous functions
var args = function(n){return n;} //return arguments to feed n'th asynch function }
for (n=0;n<N;n++)
{
(function(n){
var a = args(n);
var ftmp = n==N-1? function(){} : function(){chain.f[n+1]();};
chain.f[n] = function () {asynch(a, ftmp)}
})(n);
}
chain.f[0]() // initiate asynchronous chain
Asynchronous loops are a pain in the ass. As a rule of thumb, if you want to do them by hand you need to rewrite the for loop as a recursive function.
function sequence_synchronous(steps){
for(var i=0; i<steps.length; i++){
steps[i]();
}
return;
}
function sequence_async(steps, callback){
var i = 0;
var next_step = function(){
if(i >= steps.length){
callback();
}else{
steps[i](function(){
i++;
next_step();
});
}
}
next_step();
}
Note that this doesn't attempt to build a big chain of callbacks before calling the first one - all we did was convert the traditional for loop into continuation passing style.
I would highly recommend looking for a library to do this for you though.

Understanding closures: Constructing a meta-function that queues functions together

In terms of solving the problem, I have a fully working solution that I just finished here:
// synchronous dynamic script loading.
// takes an array of js url's to be loaded in that specific order.
// assembles an array of functions that are referenced more directly rather than
// using only nested closures. I couldn't get it going with the closures and gave up on it.
function js_load(resources, cb_done) {
var cb_list = []; // this is not space optimal but nobody gives a damn
array_each(resources, function(r, i) {
cb_list[i] = function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
if (i === resources.length-1) {
cb_done();
} else {
cb_list[i+1]();
}
};
};
});
cb_list[0]();
}
I am completely happy with this because it does what I want now, and is probably far easier to debug than what my first approach, if it had succeeded, would have been.
But what i can't get over is why I could never get it to work.
It looked something like this.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
// So this is an iterative approach that makes a nested "function stack" where
// the inner functions are hidden inside the closures.
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
var tmp_f = function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() { console.log("js_load: loaded "+r); cur_cont(); }; // TODO: get rid of this function creation once we know it works right
};
cur_cont = tmp_f; // Trying here to not make the function recursive. We're generating a closure with it inside. Doesn't seem to have worked :(
});
cur_cont();
}
It kept trying to call itself in an infinite loop, among other strange things, and it's really hard to identify which function a function is and what a function contains within it, during debugging.
I did not dig into the code, but it appears that jQuery.queue has also implemented a similar mechanism to my working one (using an array to track the queue of continuations) rather than using only closures.
My question is this: Is it possible to build a Javascript function that can take a function as argument, and enhance it with a list of other functions, by building closures that wrap functions it creates itself?
This is really hard to describe. But I'm sure somebody has a proper theory-backed mathematical term for it.
P.S. Referenced by the code above are these routines
// iterates through array (which as you know is a hash), via a for loop over integers
// f receives args (value, index)
function array_each(arr, f) {
var l = arr.length; // will die if you modify the array in the loop function. BEWARE
for (var i=0; i<l; ++i) {
f(arr[i], i);
}
}
function array_each_reverse(arr, f) {
var l = arr.length; // will die if you modify the array in the loop function. BEWARE
for (var i=l-1; i>=0; --i) {
f(arr[i], i);
}
}
The problem is how you were setting the value of cur_cont for every new function you made, and calling cur_cont in the onload callback. When you make a closure like tmp_f, any free variables like cur_cont are not 'frozen' to their current values. If cur_cont is changed at all, any reference to it from within tmp_f will refer to the new, updated value. As you are constantly changing cur_cont to be the new tmp_f function you have just made, the reference to the other functions are lost. Then, when cur_cont is executed and finishes, cur_cont is called again. This is exactly the same function that had just finished executing - hence the infinite loop!
In this sort of situation, where you need to keep the value of a free variable inside a closure, the easiest thing to do is to make a new function and call that with the value you want to keep. By calling this new function, a new variable is created just for that run, which will keep the value you need.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
// Make a new function, and pass the current value of the `cur_cont`
// variable to it, so we have the correct value in later executions.
// Within this function, use `done` instead of `cur_cont`;
cur_cont = (function(done) {
// Make a new function that calls `done` when it is finished, and return it.
// This function will become the new `cur_cont`.
return function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
done();
};
};
})(cur_cont);
});
// Start executing the function chain
cur_cont();
}
EDIT: Actually, this can be made even simpler by using the Array.reduce function. Conceptually, you are taking an array and producing a single function from that array, and each successive function generated should be dependant upon the last function generated. This is the problem that reduce was designed to help solve:
function js_load(resources, done) {
var queue = resources.reduceRight(function(done, r) {
return function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
done();
};
};
}, done);
queue();
};
Note that reduce and reduceRight are not available for older browsers (<= IE8). A JavaScript implementation can be found on the MDN page.

Categories

Resources