Javascript function scope / order of execution issue - javascript

I'm having an issue with the javascript scope / order of execution. I create an empty object inside a function. Then I add some properties in a child function. The properties haven't changed in the parent function, though.
$scope.finder = function (obj) {
var id = obj.oid;
var crit = MC.Criteria('_ownerUserOid == ?', [id]);
theResult = {}; // Scope should be finder function.
database.findOne(crit) // This is a Monaca method for finding from database
.done(function(result) {
// Returns and empty object as expected.
console.log(JSON.stringify(theResult));
theResult = result;
// Returns the search result object as again expected.
console.log(JSON.stringify(theResult));
});
// Here's the issue - when I log and return theResult, I get an empty object again.
// I think it has to do something with the scope.
// Also, this logs to console before either of the console.logs in the done function.
console.log(JSON.stringify(theResult));
return theResult;
};

I think you forgot the "var" before declaring the variable
var theResult = {}

It looks like you are performing an asynchronous request for some data. Any time you are performing asynchronous JavaScript, you will need to keep in mind that things aren't called sequentially. When an asynchronous call is made, JavaScript will carry on executing code down the stack.
In your case, theResult will be an empty object because database.findOne(crit) has not finished executing by the time you call console.log(JSON.stringify(theResult));
Because of this, you cannot return from $scope.finder, instead you could pass a callback into $scope.finder and execute that once database.findOne(crit) has finished executing.
$scope.finder = function (obj, callback) {
var id = obj.oid;
var crit = MC.Criteria('_ownerUserOid == ?', [id]);
theResult = {}; // Scope should be finder function.
database.findOne(crit) // This is a Monaca method for finding from database
.done(function(result) {
// Returns and empty object as expected.
console.log(JSON.stringify(theResult));
theResult = result;
callback(theResult);
// Returns the search result object as again expected.
console.log(JSON.stringify(theResult));
});
};
Then call it like this.
$scope.finder({some: 'data'}, function(response) {
// response now has the values of theResult
console.log(response)
});

Change it to this:
$scope.finder = function (obj) {
return database.findOne(MC.Criteria('_ownerUserOid == ?', [obj.oid]));
};
// Client code:
finder({ oid: 'foo' })
.then(function(result) { console.log(JSON.stringify(result)); });

Related

Javascript - Local object variable not conserved between promise and await

I am using the code below which higlights the fact that local object variable (HitCurrent) is not conserved between the core of computeHit(HitCurrent,'computer') and after the calling of this function in the following way : await computeHit(HitCurrent, 'computer');
In the core of computeHit(HitCurrent,'computer'), array HitCurrent.arrayCurrent is modified (actually, it computes the hit for computer) : But the issue is that modifications are not conserved once I come back to the main thread (after await computeHit(HitCurrent, 'computer').
If I do, after the await, a console.log of an array contained in this local object variable, I don't get the same array than one which is right computed into core of computeHit function. I don't understand this behavior.
// Perform hit computer
(async () => {
// Wait computeHit function
await computeHit(HitCurrent, 'computer');
// Output array which is not the same than into computeHit
console.log('into async : HitCurrent.arrayCurrent', HitCurrent.arrayCurrent);
alert('into async() function');
})();
}
with computerHit function like :
function computeHit(HitCurrent, mode) {
if (mode == 'computer') {
return new Promise( resolve => {
// Creation of webworker
let firstWorker = new Worker(workerScript);
firstWorker.onmessage = function (event) {
resolve(event.data);
}
// Post current copy of HitCurrent, i.e HitCurrent
firstWorker.postMessage([HitCurrent, HitCurrent.playerCurrent, maxNodes]);
}).then(({result}) => {
// Get back game board of webworker
HitCurrent = result.HitResult;
// Get back suggested hit computed by webworker
[a,b] = HitCurrent.coordPlayable;
// Drawing all lines from suggested hit (in 8 directions)
// HitCurrent.arrayCurrent is modified HERE !
for (k = 0; k < 8; k++) {
exploreHitLine(HitCurrent, a, b, k, 'drawing');
}
// Remove playable hits
cleanHits('playable', HitCurrent);
// Display current game
displayCurrentHit(HitCurrent);
// Output array which is good
alert('into computeHit function');
console.log('into promise - HitCurrent.arrayCurrent', HitCurrent.arrayCurrent);
})
}
}
I expect to get back the same array (i.e HitCurrent.arrayCurrent) between the computation of it into computeHit function and after the await call, but this is not the case.
How could I fix this issue ? I tried to pass by global variables, without success. Feel free to ask me further informations if you need it.
Let's rewrite your code just a little and rename some variables.
Literally, I've renamed the parameters of computeHit(..) to my_variable_1 and my_variable_2.
You may want to check/admit this does not change the code execution.
You should now understand why the HitCurrent in your async code block is never modified. Refer to simplified code block 3, which will yield 42. Hope it helps.
Code block 1 unchanged :
// Perform hit computer
(async () => {
// Wait computeHit function
await computeHit(HitCurrent, 'computer');
// Output array which is not the same than into computeHit
console.log('into async : HitCurrent.arrayCurrent', HitCurrent.arrayCurrent);
alert('into async() function');
})();
Code block 2 with renamed variables :
function computeHit(my_variable_1, my_variable_2) {
if (my_variable_2 == 'computer') {
return new Promise( resolve => {
// Creation of webworker
let firstWorker = new Worker(workerScript);
firstWorker.onmessage = function (event) {
resolve(event.data);
}
// Post current copy of my_variable_1, i.e my_variable_1
firstWorker.postMessage([my_variable_1, my_variable_1.playerCurrent, maxNodes]);
}).then(({result}) => {
// Get back game board of webworker
my_variable_1 = result.HitResult;
// Get back suggested hit computed by webworker
[a,b] = my_variable_1.coordPlayable;
// Drawing all lines from suggested hit (in 8 directions)
// my_variable_1.arrayCurrent is modified HERE !
for (k = 0; k < 8; k++) {
exploreHitLine(my_variable_1, a, b, k, 'drawing');
}
// Remove playable hits
cleanHits('playable', my_variable_1);
// Display current game
displayCurrentHit(my_variable_1);
// Output array which is good
alert('into computeHit function');
console.log('into promise - my_variable_1.arrayCurrent', my_variable_1.arrayCurrent);
})
}
}
Code block 3, simplified :
// Perform hit computer
var magicNumber = 42;
(async () => {
await computeHit(magicNumber, 'computer');
console.log('magic is ', magicNumber);
})();
function computeHit(my_variable_1, my_variable_2) {
if (my_variable_2 == 'computer') {
// Get back game board of webworker
my_variable_1 = 314159;
}
}
Assigning to a parameter within a function has no effect whatsoever on any variable used as the argument for that parameter in the call. Simplifying your code:
function computeHit(HitCurrent) {
HitCurrent = "something else";
}
let HitCurrent = "something";
computeHit(HitCurrent);
console.log(HitCurrent); // "something", not "something else"
There is no link whatsoever between the HitCurrent variable in computeHit(HitCurrent) and the HitCurrent parameter inside the call to computeHit, other than that the value of the first was read and passed into the function as the value of the second.
If you want to update HitCurrent based on what happens in computeHit, either:
Return the new HitCurrent and assign it back to the variable: HitCurrent = computeHit(...
Make HitCurrent refer to an object, and modify the object's state (e.g., use properties on it).

Can't change global variable inside function (JavaScript) [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 1 year ago.
I have a global variable called result and a function, with the purpose of changing the value of result. here's the code I've tried:
checkdate();
function checkdate() {
//defining startdate,enddate,hotel_id
$.ajax({
method: 'GET',
url: '/checkdate',
data: {startdate : startdate, enddate : enddate, checkroom : 1, hotel_id : hotel_id},
success: function(response){
storeResponse(response);
}
});
}
var result = [];
function storeResponse(response) {
window.result = response;
}
alert(result);
The alert returns nothing, however if I put the alert INSIDE the function, it returns response alright. This seems to be easy but I can't figure it out.
The function is invoked before all this code.
There are two things you need to know here:
var result is not same as window.result so use window.result = "Test"; and not the var declaration.
You need to invoke storeResponse() before alert code so that it set the new value and then get that value in alert.
window.result = "Test"; //in global scope
function storeResponse(response) {
window.result = response;
console.log(window);
}
storeResponse("Hello");
alert(result);
You should call the function first so that the result variable is populated.
var result = []; //in global scope
function storeResponse(response) {
window.result = response;
}
storeResponse('callSomething');
alert(result);
You said you are invoking the function first, so you must have something like this:
storeResponse('someResponse');
var result = []; //in global scope
function storeResponse(response) {
window.result = response;
}
alert(result);
The issue is the following:
In first line you are calling your function. The function sets a new result var in the window, global scope
In the second line, you are overwriting the result var: var result = []; It lets you with an empty array, that's why the alert looks empty
Try commenting the second line and it will work:
storeResponse('someResponse');
//var result = []; //in global scope
function storeResponse(response) {
window.result = response;
}
alert(result);
Or even better, declare the var first:
var result = [];
storeResponse('someResponse');
function storeResponse(response) {
result = response; // You don't need to use window (if the var is not declared inside the function, is global by default)
}
alert(result);
Thats because AJAX is asynchronous. Meaning the code is non-blocking.
Imagine the AJAX call being passed to another thread for processing while the rest of the code is executed. The alert is triggered before the AJAX request has received it's response. Thats why if you do the alert inside the callback it works.

Chrome Extension | Is there any way to make chrome.storage.local.get() return something?

in my chrome extension I need to use chrome storage. In my background script first I create an object and add it to chrome storage and then I want to get my object from there and to be returned. Something like that:
...
var obj = {};
chrome.storage.local.set(obj, function () { });
...
var data = getData(obj); // I want my object to be returned here
var returnedData = null;
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
returnedData = result; // here it works, I can do something with my object
});
return returnedData; // here it doesn't work
}
As far as I understood from here chrome.storage.local.get is asynchronous with its consequences. But is there any way how to get something from chrome storage and make it to be returned? I mean maybe I should wrap chrome.storage.local.get in another function or so?
Many thanks in advance!
If you want to stay away from global variables and you're okay with modern browser requirements, then you can implement a native JavaScript Promise object. For example, here's a function that returns the stored data for a single given key:
function getData(sKey) {
return new Promise(function(resolve, reject) {
chrome.storage.local.get(sKey, function(items) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
reject(chrome.runtime.lastError.message);
} else {
resolve(items[sKey]);
}
});
});
}
// Sample usage given this data:
// { foo: 'bar' }
getData('foo').then(function(item) {
// Returns "bar"
console.log(item);
});
If you need support for IE11 and below, then you'll have to turn to a library like jQuery.
No it's not possible
But there are several ways around this problem
Do everything you want to do with the data returned from .get() inside the callback (or start it from there using function calls). This is what #wernersbacher posted
Take a look at deferreds (jQuery or Q libraries). A deferred's promise can be returned from getData. Inside the .get() callback, you can resolve the deferred. Outside of getData you can use .then() to do something after the deferred resolved
Something like this
function getData(obj) {
var deferred = $.Deferred();
chrome.storage.local.get(obj, function(result) {
deferred.resolve(result);
});
return deferred.promise();
}
$.when(getData(obj)).then(function(data) {
// data has value of result now
};
You have to do it like that:
var returnedData = null;
function setData(value) {
returnedData = value;
}
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
setData(result); // here it works, I can do something with my object
});
return; // here it doesn't work
}
..because you tried to return a value which did not get read from storage yet, so it's null.
Update with Manifest V3 :
Now chrome.storage.local.get() function returns a promise that you can chain or can await in an async function.
const storageCache = { count: 0 };
// Asynchronously retrieve data from storage.local, then cache it.
const initStorageCache = chrome.storage.local.get().then((items) => {
// Copy the data retrieved from storage into storageCache.
Object.assign(storageCache, items);
});
Note : You must omit the callback paramter to get the promise.
Reference : https://developer.chrome.com/docs/extensions/reference/storage/#:~:text=to%20callback.-,get,-function
You need to handle it with callback functions. Here are two examples. You use a single function to set, however you create a separate function for each "On Complete". You could easily modify your callback to pass additional params all the way through to perform your needed task.
function setLocalStorage(key, val) {
var obj = {};
obj[key] = val;
chrome.storage.local.set(obj, function() {
console.log('Set: '+key+'='+obj[key]);
});
}
function getLocalStorage(key, callback) {
chrome.storage.local.get(key, function(items) {
callback(key, items[key]);
});
}
setLocalStorage('myFirstKeyName', 'My Keys Value Is FIRST!');
setLocalStorage('mySecondKeyName', 'My Keys Value Is SECOND!');
getLocalStorage('myFirstKeyName', CallbackA);
getLocalStorage('mySecondKeyName', CallbackB);
// Here are a couple example callback
// functions that get executed on the
// key/val being retrieved.
function CallbackA(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}
function CallbackB(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}

I'm using .done() incorrectly, doesn't return value as expected

I am trying to use jQuery's AJAX deferreds to return a JSON string that can be parsed and used but I have coded myself into a corner and may have screwed up the logic. I expected the results of the AJAX call to be returned in the .done() callback and they are. I thought once done that I could return the result for use in the remainder of the function. I know that I'm missing something really obvious and simple, I just cannot put a finger on what it is.
Here is the initial coding of the function, stil very much in test mode. The JSON is correctly returned in the .done() function but I cannot assign it outside of the function for use.
checkUserRoles = function(){
var userRole, foo, roles, i$, len$, i;
userRole = roleChecker();
foo = userRole.done(function(data){
var bar;
bar = foo.responseText; // correctly returns the JSON data
console.log(bar);
return bar; // is undefined, this is the problem
});
if (userRole !== false) {
userRole = jsonDecode(userRole);
} else {
userRole = "";
}
roles = userRole.split(',');
$("if-user-role").hide();
for (i$ = 0, len$ = roles.length; i$ < len$; ++i$) {
i = roles[i$];
$("if-user-role[data-role~=" + i + "]").show();
}
return true;
};
this.roleChecker = function(){
var retVal, allCookies, i$, len$, thecookie, hashedCookie, theHash, userHash, post;
retVal = "";
allCookies = document.cookie.split(';');
for (i$ = 0, len$ = allCookies.length; i$ < len$; ++i$) {
thecookie = allCookies[i$];
if (thecookie.indexOf('userHash') >= 0) {
hashedCookie = thecookie;
theHash = hashedCookie.split('=');
userHash = theHash[1];
}
}
post = $.ajax({
url: '/services/check_hash.php',
data: {
hash: userHash
},
type: "POST"
});
return post;
};
The code that you see here is the output from the compiling of LiveScript which we use extensively. I don't think the LiveScript is having an effect on the final result, I just had to do a lot to get what I expected would be the proper JavaScript / jQuery output.
NOTE: because this is more or less the first pass at the code foo doesn't get passed along to the subsequent if statement as userRole was originally hard-coded for the initial testing prior to trying to make the function more dynamic.
How do I return foo.responseText or bar for use in the subsequent procedure? Do I need to put the procedure, beginning with the if conditional in the .done() function?
You're looking for .then and not .done.
What .done does is perform an action and return the same promise. On the other hand then returns a new promise which is resolved with the return value of the callback provided to it. (Assuming the $.ajax resolved correctly).
You of course then need to place everything you subsequently do in the chain:
userRole.then(function(data){
var bar;
bar = foo.responseText; // correctly returns the JSON data
console.log(bar);
return bar;
}).then(function(role){;
if (role != false) {
role = jsonDecode(userRole);
} else {
userRole = "";
}
//...
return true;
});
You should also return that promise to hook on it later.
It looks like you are using deferred objects synchronously, which (as you mentioned in your title) is not the intended purpose. The code that you process the user data with after .done() will execute immediately after registering that handler, so your data won't be ready yet.
When you register a .then() on a deferred promise, you're telling your program to run a piece of code after the deferred object has either resolved or rejected. The program will not wait until that deferred object has resolved or rejected, it will continue processing code (which is the beauty of the deferred object system!)
Example:
var checkUserRoles = function () {
var userRole = roleChecker();
// go off and find the user data
// but *don't wait for it before continuing code execution*
userRole.then(function(data){
// at this point the AJAX request has finished, and we have the data
console.log(data);
// put code to process the user data here
});
// code here is executed immediately after registering the .then handler
// so the user data has not loaded yet
};
var roleChecker = function () {
var defer = $.Deferred();
var post = defer.promise();
// simulate an AJAX Request, returns after 2 seconds
setTimeout(function () {
defer.resolve('user data here!');
}, 2000);
return post;
};
checkUserRoles();

Access array returned from a function - javascript/jquery noob moment

When the form is submitted, I'm calling a function getPosts and passing through a variable str. What I'd like to do is get the data returned from that function.
// when the form is submitted
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
var something = getPosts(str);
* This is where I'd like to get the data returned from getPosts()
return false;
});
// get the data
function getPosts(str){
$.getJSON('http://myurl.com/json?'+str+'&callback=?',
function(data) {
arrPosts = new Array();
$.each(data.posts, function(i,posts){
// build array here
});
return arrPosts;
});
};
I've tried many things, but have only gotten 'undefined' returned. I've tried console.log(something);, console.log(getPosts).
I'm missing something very fundamental here. Any help would be greatly appreciated.
EDIT:
What I'm trying to do is create a single function that would get posts. Then different events would call that function. I could then use that data. So one event may be submitting a form, another may be clicking a link, another lazy/endless scrolling. All could use the same getPosts function.
There's a lot of parsing out the results which amounts to a lot of lines of code. Was just trying to find a way to reuse that function. Do you think that would be possible?
$('a.thisLink').click(function(){
getPosts();
get the return from getPosts() and do something with it
});
$('form.thisForm').submit(function(){
getPosts();
get the return from getPosts() and do something with it
});
function getPosts(){
get the posts and return an array
}
Ajax requests are executed asynchronously, the callback function (function (data)) of getJSON is executed when the request ends, and returning a value in that callback has no effect, because is a nested function inside getPosts and its return value is never used.
Actually in your example, getPosts doesn't return anything and it ends its execution before the data is returned.
I would recommend you to work on your submit event handler, if you want to keep the getPosts function, you can introduce a callback parameter:
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
getPosts(str, function (data) {
var array = [];
$.each(data.posts, function(i,posts){
// build array here
array.push(/* value to add */);
});
// array ready to work with...
//...
});
return false;
});
function getPosts(str, callback){
$.getJSON('http://myurl.com/json?'+str+'&callback=?', callback);
}
Edit 2: In response to your second comment, you could make another callback, that will be executed when the data has been processed by the first callback, and you can define it when you execute the getPosts function on the submit event handler:
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
getPosts(str, reusableCallback, function (result) {
// result contains the returned value of 'reusableCallback' <---
});
return false;
});
function reusableCallback(data) {
var array = [];
$.each(data.posts, function(i,posts){
array.push(/* value to add */);
});
//...
return array;
}
function getPosts(str, callback, finishCallback){
$.getJSON('http://myurl.com/json?'+str+'&callback=?', function (data) {
finishCallback(callback(data)); // pass the returned value
// of callback, to 'finishCallback' which is
// anonymously defined on the submit handler
});
}
Edit 3: I think that the getPosts function and the "reusableCallback" function are strongly related, you might want to join them, and make the code easier to use and understand:
$('form#getSome').submit(function(){
var str = $("form#getSome").serialize();
getPosts(str, function (result) {
// result contains the processed results
});
return false;
});
function getPosts(str, finishCallback){
$.getJSON('http://myurl.com/json?'+str+'&callback=?', function (data) {
// process the results here
var array = [];
$.each(data.posts, function(i,posts){
array.push(/* value to add */);
});
//...
finishCallback(array); // when the array is ready, execute the callback
});
}
Your getPosts function looks incomplete, I'm no jquery expert but should it look something like:
function getPosts(str) {
$.getJSON('http://myexample.com/json?'+str+'&callback=?',function(data){
var arrPosts = [];
$.each(data.posts, function(i,posts){
... build array yada yada ...
});
return arrPosts;
});
}
The problem is that the $.getJSON callback function gets called when the get request returns the data, not inline with your function.

Categories

Resources