I've defined a function in React-Native that gets an object from Firebase and pushes its contents onto an array. However, although the array is being populated fine after my call to 'push', it is undefined at the end of the for loop. I have no idea why it's being cleared, as I'm defining the array outside of the for loop, so I thought I'd ask for an extra pair of eyes in case I'm missing anything.
var newList = [];
var arrayLength = userArray.length;
for (var i = 0; i < arrayLength; i++) {
var userRef = usersRef.child(userArray[i]);
userRef.once('value', function(snap) {
newList.push({
name: snap.val().name,
description: snap.val().description,
});
//this log statement prints out correct array
console.log("NEWLIST" + newList[i]);
});
//but array is undefined here
console.log("NEWLIST 2" + newList[i]);
}
this.setState({
current: this.state.current.cloneWithRows(newList),
past: this.state.past.cloneWithRows(oldList)
});
The code looks fine but what I think might be happening here is that userRef.once() is an async call, and your outer console.log is getting executed first. And when the event triggers the callback your inner console.log() prints the value
Read up on JavaScript's asynchronous nature. The once function takes a callback. The console.log in the outer scope is executed before you push any items info your array.
Related
This should be simple, but it doesnt make sense to me.
It is a simple section of a promise chain
let flightName = [];
let guidArr = [];
Promise.all(guidArr)
.then(values => {
for(var a = 0; a < values.length; a++) {
Xrm.WebApi
.online
.retrieveRecord("crd80_flightplanevent", values[a], "?$select=_crd80_learnerflight_value")
.then(
function success(result) {
flightName.push(result["_crd80_learnerflight_value#OData.Community.Display.V1.FormattedValue"])
},
function(error) {
DisplayError(error)
});
}
return flightName
}).then(flightName => {
console.log(flightName)
console.log(flightName.length)
return flightName
})
The console displays the flightName array correctly,
but flightName.length is always consoled as 0, even though console.log(flightName) puts length:2 as the output
Why???
I need to work with each item in the array but it is not recognised correctly
The recommendation by #derpircher worked:
You are not awaiting the promises in your for(var a = 0; a < values.length; a++) loop. Thus, when you do return flightName the array is still empty, and thus console.log(flightName.length) prints 0. The output from console.log(flightName) might pretend, that flightName contains values, but actually it does not because that console.log is referencing the object, which is later filled when the promises resolve. Do console.log(JSON.stringify(flightName)) and you will see it prints just [] because at that moment, the array is still empty
To resolve that issue, make the first then handler async and use await Xrm.WebApi... or properly wrap that whole thing in a Promise.all
So I want to invoke addOption after I update my state, and I know that setState in React is asynchronous so I put addOption as a callback after setState completely executes:
a_method{
this.setState({
songRec: songList,
}, this.addOption);
}
However, in my addOption method, when I log out songRec I can see that it is clearly updated but its length is still 0 so the for loop is never executed.
addOption(){
const songRec = this.state.songRec;
var datalist = document.getElementById("data");
var options = '';
console.log(songRec);
console.log(songRec.length);
for (var i = 0; i < songRec.length; i++){
//some code//
}
datalist.innerHTML = options;
}
This is the output at the console. Line 86 logs songRec and line 87 logs songRec.length.
Do you know what is happening and how to get around it?
In javascript console.log does not execute immediately in the line you set it (it kinda varies on each browser), in this case, you should change it to a debugger in order to see what is really going on. Is there some specific reason to run this callback function? Can't you use the data that you set on the state to execute this other function?
The problem is mostly likely caused by the asynchronous function on array. Refer to the following example:
function asyncFunction(list){
setTimeout(function(){
list.push('a');
list.push('b');
list.push('c');
console.log(list.length); // array length is 3 - after two seconds
}, 2000); // 2 seconds timeout
}
var list=[];
//getting data from a database
asyncFunction(list);
console.log(list.length) //array is length zero - after immediately
console.log(list) // console will show all values if you expand "[]" after two secon
To fix this, a workaround you may need to use is to use a promise for the async part.
function asyncFunction(list){
return new Promise(resolve => {
setTimeout(function(){
list.push('a');
list.push('b');
list.push('c');
resolve(list);
console.log(list.length); // array length is 3 - after two seconds
}, 2000);
});
// 2 seconds timeout
}
async function getList(){
var list=[];
await asyncFunction(list);
console.log(list.length) ;
console.log(list);
}
getList();
Consider the following code snippets:
for(var i = 0; i < arr.length; ++i) {
var newObject = new someFunction(arr[i]);
//async callback function
$http.get('someurl').then(
function(data) {
newObject.data = data;
}
);
}
VS
function registerCallbacks(o) {
$http.get('someurl').then(
function(data) {
o.data = data;
}
);
}
for(var i = 0; i < arr.length; ++i) {
var newObject = new someFunction(arr[i]);
registerCallbacks(newObject);
}
The first example will perform the async operation only on the last object in the array, while the second example will work as expected.
I understand that in the first example, the callbacks all refer to the same variable 'newObject', so only one object is acted upon.
But why isn't it like this in the second example as well? Wouldn't 'o' end up referring to the parameter of the last function call?
I'm afraid I've missed something fundamental about how values are passed in javascript and would be grateful if someone could elucidate me on how it works.
Cheers!
In the first example, newObject object may not have the same value by the time asynch callBack is invoked since its scope is still there in its parent method, and the prevailing values of newObject will be used by the inner block.
However, in the second one newObject is passed to another method as o (a new reference) which doesn't have the same variables in scope since it is outside that for loop block.
I have an Async function
function AsyncFunc(args){
//does some async work here
}
then I call this function multiple times in a for loop:
for(var i=0; i<10; i++)
{
AsyncFunc(i);
}
Does this create multiple copies of the AsyncFunc? or are the local variables defined in AsyncFunc getting overridden by the subsequent calls?
EDIT
Suppose the AsyncFunc does following:
function AsyncFunc(args){
$.get(args.url, function(data){
args.data = data;
});
}
then I call this function multiple times in a for loop:
for(var i=0; i<10; i++)
{
AsyncFunc(args_object_with_a_different_url);
}
Now would data go into their corresponding args object? In other words, would the callback attach to the copy of the function in which the ajax request was initiated?
The AsynFunc() is placed on the call stack 10 times. Each call contains a localized copy of all variables and functions defined within it. Therefore, they do not share state and "get overriden".
The calls share no state with one another other than any references to objects within the global name space.
EDIT:
Example where they would potentially "share":
var mySharedVariable = 0;
function AsyncFunc(args) {
var myLocalVariable = mySharedVariable++;
console.log(myLocalVariable);
// do some asynchronous task that i'm too lazy to code for the example as it's not relevant
}
for(var i = 0; i < 10; i++)
AsyncFunc(i);
console.log(mySharedVariable);
As you can see here, if we were to output mySharedVariable at the end, it would output 10. However, if we output myLocalVariable we would see something akin to 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 since they are local variables and do not share state.
Edit:
jQuery async call example:
for (var i = 0; i < 10; i++)
$.post('someURL', function(response) {
var myLocalVariable = response.data;
//the data returned is not a copy, it's an individual instance per call invoked.
//neither is myLocalVariable shared, each invocation of this callback has it's own memory allocated to store its value
});
Edit:
To your most recent question, every data object would be unique to each callback after the get request. However, your code doesn't assure me that the args variable being passed in is different each time so there is a chance that your implementation could lead to args.data being overriden with each callback. So take a look below at another option to ensure you store all of the data objects from your callbacks appropriately.
var args = [];
function AsyncFunc(args){
$.get(args.url, function(data){
args.push(data);
});
}
//now args will have 10 uniquely different data objects stored inside.
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.