How can I manage Node.js async variable scope? - javascript

I have the following code for populating my test database. The goal is to save the parent document after each of the child documents to the parent can have a reference to them.
function saveRecipe(ingredients, directions, recipe, done) {
var ingredientSaveTasks = createSaveTasks(ingredients)
var directionSaveTasks = createSaveTasks(directions)
async.parallel([
(callback) => { async.series(ingredientSaveTasks, callback) },
(callback) => { async.series(directionSaveTasks, callback) }
], (err, results) => {
recipe.ingredients = results[0] // The returned ids for each ingredient
recipe.directions = results[1] // The returned ids for each direction
recipe.save(done)
})
}
function createSaveTasks(objs) {
var saveTasks = []
for (var i = 0; i < objs.length; i++) {
var saveTask = function (callback) {
var obj = Object.assign({}, objs[i])
obj.save((err, result) => {
callback(err, result._id)
})
}
saveTasks.push(saveTask)
}
return saveTasks
}
I've tried a few variations on this and I think it has to do with variable scope. However, I thought by deep copying my obj with var obj = Object.assign({}, objs[i]) would save a "real" copy of the object for later use inside the async function.
Depending on which of the many way I've tried to make this work I end up with one of the following errors:
TypeError: obj.save is not a function
TypeError: Cannot read property 'save' of undefined
I've seen some talk about using .bind() to control variable scope but I'm not sure how to use it in this case.

Bind return a new function and you can specify the context assigned to the function (the value that you obtain when call "this" keyword inside the function) and/or the arguments passed to the function. Bind(thisArg, ...arguments). So in your case:
var saveTask = function(callback){this.save(...)}.bind(obj[i])
With this you specify the context, so in that function your object will be accesible as this.
Some examples:
(function a(){console.log(this)}).bind({key : 'value'})();
var a = function(){console.log(this)}.bind({key : 'value'});
a();

Related

Object modified in the function is not being modified outside the function

I'm building a code to parse some JSON details received from the server into a javascript object. The object has many objects inside it.
Then I have another function to create HTML element and apply that object's values (using for - in loop) into HTML tags' "innerHTML".
I have included the code i use below,
// This one is executed on the 'onLoad' event.
function requestDriverListings() {
**//This object stores the received object from server.**
var drivers = {};
// ***This function requests details from the server and the function in the arguments is executed once the details are received.***
sendUserData ({}, "request driver.php", function (request) {
listDrivers(request,drivers); console.log(drivers); displayDrivers(drivers);});
}
This one is the function to create a HTML Element and stores the received data in it and the use JSON.parse() to parse them into a Object.
The driver parameter is the Object passed in the above code.
request parameter has no effect on this problem. (It is the XHR responseText.)
function listDrivers (request,driver) {
var response = document.createElement("html");
response.innerHTML = request;
driver = response.querySelector("#drivers").innerHTML;
var stripComma = driver.lastIndexOf(",");
driver = JSON.parse(driver.substring(0,stripComma) +"}");
}
Here is the displayDrivers function.
drivers Object is passed into driveParsed in the first function.
requestAPage() is a function to request the displaying element from the server. the function in it's arguments is the function to apply the Objects details into the HTML innerHTML.
function displayDrivers (driveParsed) {
var driverElement = document.createElement("div");
driverElement.id = "driverElement";
driverElement.style.display = "none";
document.getElementById("driverContainer").appendChild(driverElement);
requestAPage("Drivers.html", "drivers", "driverElement", function() { selectDrivers();});
var selectDrivers = function () {
for (var x=0; x<=Object.keys(driveParsed).length; x++) {
var driverParsed = driveParsed[x];
setDriversDetails(driveParsed,x);
var element = createAElement( "div", {"margin-top": "10px;"});
element.id = driveParsed.name;
element.className = "container border";
element.innerHTML = driverElement.innerHTML;
document.getElementById("driverContainer").appendChild(element);
}
};
}
================================================================
My problem is this displayDrivers() is not getting the modified drivers Object.
Please help me to solve this problem. Sorry for the long description.
One problem is that inside listDrivers you assign a new value to the driver variable (which is an argument). This means the original variable, drivers, that was passed to the function as second argument, is disconnected from the local function variable driver: they are now two distinct, unrelated objects.
If you want the drivers variable to get a value from calling the function, then let that be the return value of the function, so you would call it like this:
sendUserData ({}, "request driver.php", function (request) {
var drivers = listDrivers(request); // <-----
console.log(drivers);
displayDrivers(drivers);
});
Then the listDrivers function would look like this:
function listDrivers (request) { // <--- only one argument
// declare new variable:
var driver = response.querySelector("#drivers").innerHTML;
// ... rest of your code comes here ...
// ... and finally:
return driver; // <---- return it
}
#trincot beat me to it and his answer is better. I'll leave this up anyway though.
Try doing this in requestDriverListings:
function requestDriverListings() {
var drivers = {};
sendUserData ({}, "request driver.php", function (request) {
var updatedDrivers = listDrivers(request,drivers);
console.log(drivers);
displayDrivers(updatedDrivers);});
}
And this in listDrivers:
function listDrivers (request,driver) {
var response = document.createElement("html");
response.innerHTML = request;
driver = response.querySelector("#drivers").innerHTML;
var stripComma = driver.lastIndexOf(",");
driver = JSON.parse(driver.substring(0,stripComma) +"}");
return driver;
}

.map() unable to access Object's this.function

Thanks in advance for any responses:
I don't think this is a duplicate: I reviewed that article in the first comment, that is just a general breakdown of objects and using "this" within javascript.
My other this.function's perform just fine, so I at least have the basics of JS Obj's figured out.
This issue is related to using .map() with a this.function within a constructed object.
The following Google Appscript code uses .map() to update a string in a 2d array. [[string, int],[string, int]]
For some reason, when using .map() it is am unable to access the function "this.removeLeadingZero". If that same function is placed outside of the OBJ it can be called and everything works just fine. For some reason the system claims row[0] is an [object, Object] but when I typeof(row[0]) it returns "string" as it should.
Error: TypeError: Cannot find function removeLeadingZero in object [object Object]. (line 106, file "DEEP UPC MATCH")
Is there any issue using this.function's with .map() inside an object or am I using an incorrect syntax?
function test2DMapping(){
var tool = new WorkingMappingExample()
var boot = tool.arrayBuild();
Logger.log(boot)
}
function WorkingMappingExample(){
this.arr= [["01234", 100],["401234", 101],["012340", 13],["01234", 0422141],["01234", 2],["12340",3],["01234", 1],["01234", 2],["12340",3],["01234", 1],["01234", 2],["12340",3],["01234", 1],["01234", 2],["12340",3]];
//mapping appears faster that normal iterations
this.arrayBuild = function(){
var newArray1 =
this.arr.map( function( row ) {
**var mUPC = removeLeadingZero2(row[0])** //working
**var mUPC = this.removeLeadingZero(row[0])** // not working
var index = row[1]
Logger.log(mUPC + " " + index)
row = [mUPC, index]
return row
} )
return newArray1;
};
}; //end of OBJ
//THE NEXT 2 FUNCTIONS ARE WORKING OUTSIDE OF THE OBJECT
function removeLeadingZero2(upc){
try {
if (typeof(upc[0]) == "string"){
return upc.replace(/^0+/, '')
} else {
var stringer = upc.toString();
return stringer.replace(/^0+/, '')
}
} catch (err) {
Logger.log(err);
return upc;
}
}
function trimFirstTwoLastOne (upc) {
try {
return upc.substring(2, upc.length - 1); //takes off the first 2 #'s off and the last 1 #'s
} catch (err) {
Logger.log(err);
return upc;
}
}
Inside the function that you pass to map, this doesn't refer to what you think it does. The mapping function has its own this, which refers to window, normally:
var newArray1 = this.arr.map(function(row) {
// this === window
var mUPC = this.removeLeadingZero(row[0]);
var index = row[1];
Logger.log(mUPC + " " + index);
return [mUPC, index];
});
You have four options:
Array#map takes a thisArg which you can use to tell map what the this object in the function should be:
var newArray1 = this.arr.map(function(row) {
// this === (outer this)
var mUPC = this.removeLeadingZero(row[0]);
// ...
}, this); // pass a thisArg
Manually bind the function:
var newArray1 = this.arr.map(function(row) {
// this === (outer this)
var mUPC = this.removeLeadingZero(row[0]);
// ...
}.bind(this)); // bind the function to this
Store a reference to the outer this:
var self = this;
var newArray1 = this.arr.map(function(row) {
// self === (outer this)
var mUPC = self.removeLeadingZero(row[0]);
// ...
});
Use an arrow function:
var newArray1 = this.arr.map(row => {
// this === (outer this)
var mUPC = this.removeLeadingZero(row[0]);
// ...
});
Additionally, you could stop using this and new.
I have solved this issue and below is the answer in case anyone else runs into this:
this needs to be placed into a variable:
var _this = this;
and then you can call it within the object:
var mUPC = _this.removeLeadingZero(row[0])
Javascript scope strikes again!

JavaScript Object is set but is null when accessed

I have the following object that constructs a session variable:
var sessionObject = function (key) {
this._key = key;
this._content;
this.set = function (v) {
this.setLocal(v);
$.post('/Program/SetVariable',
{ key: this._key, value: v }, function (data) {
});
};
this.get = function (callback) {
var setterCallback = this.setLocal;
$.get('/Program/GetVariable',
{ key: this._key }, function (data) {
setterCallback(data);
}).done(function () {
callback();
});
};
this.setLocal = function (v) {
this._content = v;
};
this.getLocal = function () {
return this._content;
};
}
And my C# in the controller is as follows:
public ActionResult SetVariable(string key, string value)
{
Session[key] = value;
return this.Json(new { success = true });
}
public ActionResult GetVariable(string key)
{
return this.Json(Session[key], JsonRequestBehavior.AllowGet);
}
I create a new session object every time the page is loaded, which references items in the session located on the server. When the session is set with the set() function, _content is set correctly and is able to be accessed publicly through item.getLocal() (either in the browser console or in code).
When I revisit the page and the session object referring to said item is already created, when I run the item.get() function it accesses the session variable and sets it to the _content object, I know this because I can do a console.log(this._content) in the setLocal() function which shows that the variable has been set correctly. But when I wish to access the content of the session object via either this.getLocal() or item._content while through the browser console or other lines of the code I get undefined returned to me.
So to illuminate the process some more this is what I do on a reload where there is already data in the session:
var item = new sessionObject("item");
item.get(printData);
function printData() {
$("printbox").append(item.getLocal());
}
This does not print anything.
Is there a reason I can not access this item's content unless it is specifically set by the item.set function?
Because you do this:
var setterCallback = this.setLocal;
and call it like so:
setterCallback(data);
You have lost the context of your sessionObject instance, so the this inside the setLocal function is no longer your object instance but the global window object.
You can do two things to correct this, save a reference to this instead of saving a reference to the function and call setLocal from that reference
var that = this;
/.../
that.setLocal(data);
or you can bind object instance when you save the setLocal reference
var setterCallack = this.setLocal.bind(this);

How to return array

I am using the below function to load xml and then return the array with values.
But when i call it in another function it gives error "arrXML is undefined".
function readXML() {
// create an array object
var arrXML = new Array();
//create XML DOM object
var docXML = Sys.OleObject("Msxml2.DOMDocument.6.0");
// load xml
docXML.load("C:\\Users\\ankit\\Desktop\\read.xml");
// search nodes with 'config' tag
var Nodes = docXML.selectNodes("//config");
for (i = 0; i < Nodes.length; i++){
var ChildNodes = Nodes.item(i);
arrXML[i] = Nodes(i).childNodes(0).text +":"+Nodes(i).childNodes(1).text;
}
// return array of XML elements
return arrXML;
}
function getvalues() {
log.message(arrXML[1]); // this line gives error
}
arrXML is local to the function readXML because you declared it with the var keyword inside that block. getValues has no idea it exists (because it no longer does).
Your options are to make the variable global (which you should be careful with)
vinu = {}; // vinu is global namespace containing the array
function readXML() {
vinu.arrXML = [];
// ...
return vinu.arrXML; // This might not even be necessary in this case
}
function getvalues() {
log.message(vinu.arrXML[1]);
}
... or to pass the variable to the function when you call it.
function getvalues(arg) {
log.message(arg[arrXML[1]]);
return arg; // This function can't change the original variable, so use the return if need-be
}
// Somewhere that has access to the actual "arrXML"
getvalues(arrXML);
... or use a closure.

Why can I not access an array in factory while I am in a forEach loop with Angular?

I have the following question: Why is it impossible for me to access my array in a forEach loop with Angular. I have made this factory with an array and a function, within the function I have this forEach loop. Outside of the forEach loop I can acces my array with the this keyword. In the forEach loop it gives me a undefined value.
.factory("sendOrder", function () {
return {
paired: [],
send: function () {
var names = document.getElementsByTagName('input');
var ordered = document.getElementsByClassName('ordered');
var i = 0;
console.log(this.paired);//I can access it from here
angular.forEach(names, function (amount, key) {
console.log(this.paired);//Unable to access
i++;
return;
})
}
}
})
Maybe this will help. Angular lets you set the context (this) in forEach. It's one of the arguments. You don't have to set any other variables:
angular.forEach(obj, iterator, [context]);
You've passed in obj and iterator. Just pass in something for context and that will be this in the function.
Because the context of the function changes - this is not what it originally was. The usual fix is to set the original context to a variably (usually called self):
.factory("sendOrder", function () {
return {
paired: [],
send: function () {
var self = this;
var names = document.getElementsByTagName('input');
var ordered = document.getElementsByClassName('ordered');
var i = 0;
console.log(this.paired);//I can access it from here
angular.forEach(names, function (amount, key) {
console.log(self.paired);//should be fine now
i++;
return;
})
}
}
})

Categories