Node.js Function returning undefined? [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
Trying to find a value in an excel file using the XLSX library:
The function works, it finds the value, however the output is undefined, even though the debugging say the value is found.
Here's the function:
var getValsFromExcel = function(sheet,idcol, valcol, val){
var workbook = new Excel.Workbook();
workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx')
.then(function() {
var worksheet = workbook.getWorksheet(sheet);
worksheet.eachRow({ includeEmpty: false }, function(row, rowNumber) {
console.log("Row " + rowNumber + " = " + JSON.stringify(row.values));
console.log(row.values[idcol]);
console.log('checking ' + row.values[idcol] + ' = ' + val + ' ' + (row.values[idcol] == val))
if (row.values[idcol] == val){
console.log('Value found! its ' + row.values[valcol])
//getValsFromExcel = row.values[valcol];
return row.values[valcol];
}
});
});
}
var ans = getValsFromExcel('yesno',3, 4, tobj["respondent_consent"]);
console.log('Q1 answer = ' + ans);
Here's the console output:
Q1 answer = undefined
Row 1 = [null,"UID","Delete(Y/N)","field: yesno_key_value","field: yesno_display_text"]
field: yesno_key_value
checking field: yesno_key_value = yes false
Row 2 = [null,"5b45fe42f7fe481d8442d5e94b894b45","N","yes","Si"]
yes
checking yes = yes true
Value found! its Si
Row 3 = [null,"b65ba5a1a3814a87b4571e8d477307aa","N","no","No"]
no
checking no = yes false

getValsFromExcel asynchronous, here is the correction:
var getValsFromExcel = function(sheet,idcol, valcol, val){
var workbook = new Excel.Workbook();
return workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx')
.then(function() {
var worksheet = workbook.getWorksheet(sheet);
let answer = null;
worksheet.eachRow({ includeEmpty: false }, function(row, rowNumber) {
console.log("Row " + rowNumber + " = " + JSON.stringify(row.values));
console.log(row.values[idcol]);
console.log('checking ' + row.values[idcol] + ' = ' + val + ' ' + (row.values[idcol] == val))
if (row.values[idcol] == val){
console.log('Value found! its ' + row.values[valcol])
//getValsFromExcel = row.values[valcol];
answ = row.values[valcol];
return;
}
});
return answer;
});
}
getValsFromExcel('yesno',3, 4, tobj["respondent_consent"])
.then( answer => console.log('Q1 answer = ' + ans) );

There are a couple things going on here. First, you're not returning any value from getValsFromExcel, so doing var ans = getValsFromExcel() will always be undefined (the default return value of any function).
But even if you do return workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx').then() // ... you won't get the value you have in console.log('Value found! its ' + row.values[valcol]), because workbook.xlsx.readFile returns a Promise.
If you're on a recent version of Node, you can add that return before your readFile call, and then do this
async function main() {
var ans = await getValsFromExcel()
console.log(ans)
}
main()
Edit: sorry for the half answer initially. I don't like the SO editor and apparently I smashed a key combo that prematurely saved it.
Here's another solution showing this without async/await. Remember: async/await is just Promises with special syntax, but it works the same.
getValsFromExcel(/* args */)
.then((value) => {
console.log(value)
})
This is the same thing. We either have to await the Promise, or we have to chain a then with a callback that will be invoked with the value you return in your Promise chain in the getValsFromExcel function.
There were a number of bugs in the original code... here's a further breakdown for completeness' sake:
const Excel = require("exceljs")
var getValsFromExcel = function (sheet, idcol, valcol, val) {
var workbook = new Excel.Workbook()
return workbook.xlsx
.readFile(__dirname + "/assets/gu.xlsx")
.then(function () {
var worksheet = workbook.getWorksheet(sheet)
// let's initialize with some value.
// undefined would work as well, but we can be explicit if it's not found
// and make it null.
let result = null
worksheet.eachRow({ includeEmpty: false }, function (row, rowNumber) {
if (row.values[idcol] == val) {
// ok now we reassign result to the value we want
result = row.values[valcol]
}
})
// In order to have access to the value in the next `then`
// of your Promise chain, you _must_ return it.
return result
})
.then((value) => {
console.log("Value = " + value)
})
}
// alternate version using async/await
var getValsFromExcel = async function (sheet, idcol, valcol, val) {
var workbook = new Excel.Workbook()
// wait for the asynchronous code to resolve
await workbook.xlsx.readFile(__dirname + "/assets/gu.xlsx")
// after this point, workbook has been mutated and now contains the file's data
var worksheet = workbook.getWorksheet(sheet)
let result = null
worksheet.eachRow({ includeEmpty: false }, function (row, rowNumber) {
if (row.values[idcol] == val) {
result = row.values[valcol]
}
})
console.log("Value = " + result)
}
getValsFromExcel("Sheet1", 2, 2, "Dulce")

Related

Need JavaScript example for fetch() etc

I want to learn such new JavaScript features as fetch() and arrow functions. To this end, I selected a function from a recent app, and attempted to replace older features with new. Very little success. Here's my original function:
function popNames(arNumbers,ctrlName) {
var arSortedList = [];
var strNameList = "";
$.getJSON("NAME.json").done(function(zdata) {
$.each(arNumbers, function(i, ydata) {
$.each(zdata.NAME, function(k,v) {
if(v.idName == ydata) {// important: === did NOT work
if(ctrlName) arSortedList.push(v.last + ", " + v.first + ";" + v.idName);
else arSortedList.push(v.last + ", " + v.first);
}
}); // each element of NAME.json
}); // each idName value in the array passed
if(ctrlName) {
setOptions(arSortedList, ctrlName);
} else {
strNameList = arSortedList.join();
}
}); // getJSON NAME
}
I was successful using this line:
fetch("NAME.json").then(zdata => zdata.json())
but nothing I did after that worked. I'd appreciate seeing an example from which I can learn.
function popNames(arNumbers,ctrlName) {
let arSortedList = [];
let strNameList = "";
fetch("NAME.json").then(zdata => zdata.json())
.then(zdata => {
for(const ydata of arNumbers) {
for(const v of zdata.NAME) {
if(v.idName == ydata) { // important: === did NOT work
if(ctrlName) arSortedList.push(v.last + ", " + v.first + ";" + v.idName);
else arSortedList.push(v.last + ", " + v.first);
}
}
}
if(ctrlName) {
setOptions(arSortedList, ctrlName);
} else {
strNameList = arSortedList.join();
}
}); // getJSON NAME
}
I was researching why I couldn't next two Array.forEach statements, and discovered a new iterable construction (for...of).

Node.js Promise within Promise within Promise

I think I'm trying to call a Promise within a Promise (maybe even within another Promise). In the past I try to simplify my question and I end up getting more questions, so there's a lot below:
I have the following code as a module named myModule:
let https = require('https');
module.exports.getApiOne = function(value) {
var params = {..., path = '/api/getOne/' + value, ...};
return getApi(params).then(response => response);
};
module.exports.getApiTwo = function(value) {
var params = {..., path = '/api/getTwo/' + value, ...};
return getApi(params).then(response => response);
};
function getApi(params) {
return new Promise(function(resolve, reject) {
var req = https.request(params, function(res) {
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = Buffer.concat(body).toString();
} catch (e) {
reject(e);
}
resolve(body);
});
});
req.on('error', function(err) {
reject(err);
});
req.end();
});
}
On another file I have:
const status = require('myModule');
var someObject = {};
function someFunction(inputObject) {
// initialize object
if (!someObject[inputObject.Id]) {
someObject[inputObject.Id] = {};
someObject[inputObject.Id].contact = {};
}
// get object
var objectForThis = someObject[inputObject.Id];
switch (inputObject.someVal) {
case 'test':
//... some code
objectForThis.stage = 'test';
break;
case 'hello':
status.getApiOne('world').then(response => {
console.log(response);
objectForThis.stage = 'zero'
});
break;
default:
someOtherFunction(objectForThis.stage).then(response => {
objectForThis.stage = response;
});
break;
}
someObject[inputObject.Id] = objectForThis;
}
function someOtherFunction(stage) {
var newStage;
new Promise(function(resolve, reject) {
switch (stage) {
case 'zero':
// some code
newStage = 'one';
case 'one':
status.getApiTwo('foo').then(response => {
console.log(response);
newStage = 'two';
/********************************************
I assume, the problem lies here, it's
'resolving' (below) before this sets the new
value for 'newStage'
********************************************/
});
break;
default:
// do nothing
break;
}
});
resolve(newStage);
}
When I call
someFunction({id = 1, someValue = 'test'}); // sets 'stage' to 'test'
someFunction({id = 1, someValue = 'hello'}); // sets 'stage' to 'zero'
someFunction({id = 1, someValue = 'foo'}); // sets 'stage' to 'one'
someFunction({id = 1, someValue = 'bar'}); // NOT setting 'stage' to 'two'
The reason for why is because Promises are asynchronous:
logOut("start of file");
new Promise(function(accept){
accept();
}).then(function(){
logOut("inside promise");
});
function makePromise(name) {
new Promise(function(accept){
accept();
}).then(function(){
logOut("inside promise inside makePromise " + name);
});
};
logOut("value returned from makePromise: " + makePromise("one"));
try {
// just to prove this
makePromise("two").then(function(accept){
accept();
}).then(function(){
logOut("after makePromise");
});
} catch(err) {
logOut("Failed to `.then` the return value from makePromise because:\n" + err.message);
}
logOut("end of file");
var outputList;
function logOut(str){
outputList = outputList || document.getElementById("output");
outputList.insertAdjacentHTML("beforeend", "<li><pre>" + str + "</pre></li>");
}
<ol id="output"></ol>
As seen above, the whole program does not pause for the .then statement. That is why they are called Promises: because the rest of the code goes on while the Promise is waiting to be resolved. Further, as seen above, there can be a value returned from a function only if the function explicitly returns the value via the then keyword. JavaScript functions do not automatically return the value of the last executed statement.
Please see my website here for more info on Promises.
In the demo below, I have attempted to fix up the fragments of the files you slapped on this question. Then, I proceeded to wrap them together into a quick single-file system I typed up
(function(){"use strict";
// NOTE: This setup code makes no attempt to accurately replicate the
// NodeJS api. This setup code only tries to concisely mimics
// the NodeJS API solely for the purposes of preserving your code
// in its present NodeJS form.
var modules = {}, require = function(fileName){return modules[fileName]};
for (var i=0; i < arguments.length; i=i+1|0)
arguments[i]({exports: modules[arguments[i].name] = {}}, require);
})(function https(module, require){"use strict";
////////////// https.js //////////////
module.exports.request = function(options, withWrapper) {
var p, when = {}, wrapper = {on: function(name, handle){
when[name] = handle;
}, end: setTimeout.bind(null, function(){
if (p === "/api/getOne/world") when.data("HTTP bar in one 1");
else if (p === "/api/getTwo/foo") when.data("HTTP foo in two 2");
else {console.error("Not stored path: '" + p + "'");
return setTimeout(when.error);}
setTimeout(when.end); // setTimeout used for asynchrony
}, 10 + Math.random()*30)}; // simulate random http delay
setTimeout(withWrapper, 0, wrapper); // almost setImmediate
return p = options.path, options = null, wrapper;
};
/******* IGNORE ALL CODE ABOVE THIS LINE *******/
}, function myModule(module, require) {"use strict";
////////////// myModule.js //////////////
const HttpsModule = require('https');
module.exports.getApiOne = function(value) {
var params = {path: '/api/getOne/' + value};
// There is no reason for `.then(response => response);`!
// It does absolutely nothing.
return getApi(params); // .then(response => response);
};
module.exports.getApiTwo = function(value) {
var params = {path: '/api/getTwo/' + value};
return getApi(params); // .then(response => response);
};
function getApi(params) {
return new Promise(function(resolve, reject) {
var req = HttpsModule.request(params, function(res) {
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = body.join("");//Buffer.concat(body).toString();
} catch (e) {
reject(e);
}
resolve(body);
});
});
req.on('error', function(err) {
reject(err);
});
req.end();
});
}
}, function main(module, require) {"use strict";
////////////// top JS script //////////////
const MyStatusModule = require('myModule');
const isPromise = isPrototypeOf.bind(Promise.prototype)
var someObject = {};
function someFunction(inputObject) {
// initialize object
// NOTE: Javascript IS case-sensitive, so `.Id` !== `.id`
if (!someObject.hasOwnProperty(inputObject.id)) {
someObject[inputObject.id] = {};
someObject[inputObject.id].contact = {};
}
// get object
var objectForThis = someObject[inputObject.id];
switch (inputObject.someValue) {
case 'test':
//... some code
return objectForThis.stage = 'test';
break;
case 'hello':
return MyStatusModule.getApiOne('world').then(function (response) {
// console.log(response);
return objectForThis.stage = 'zero'
});
break;
default:
return someOtherFunction(objectForThis.stage).then(function (response) {
return objectForThis.stage = response;
});
break;
}
}
function someOtherFunction(stage) {
var newStage;
// If you return nothing, then you would be calling `.then` on
// on `undefined` (`undefined` is the default return value).
// This would throw an error.
return new Promise(function(resolve, reject) {
switch (stage) {
case 'zero':
// some code
newStage = 'one';
resolve(newStage); // you must call `resolve`
case 'one':
return MyStatusModule.getApiTwo('foo').then(function (response) {
// console.log(response);
newStage = 'two';
/********************************************
I assume, the problem lies here, it's
'resolving' (below) before this sets the new
value for 'newStage'
********************************************/
resolve(newStage); // you must call `resolve`
});
break;
default:
// do nothing
resolve(newStage); // you must call `resolve`
break;
}
});
}
// tests:
function logPromise(){
var a=arguments, input = a[a.length-1|0];
if (isPromise(input)) {
for (var c=[null], i=0; i<(a.length-1|0); i=i+1|0) c.push(a[i]);
return input.then(logPromise.bind.apply(logPromise, c));
} else console.log.apply(console, arguments);
}
logPromise("test->test: ", someFunction({id: 1, someValue: 'test'})); // sets 'stage' to 'test'
logPromise("hello->zero: ", someFunction({id: 1, someValue: 'hello'})) // sets 'stage' to 'zero'
.finally(function(){ // `.finally` is like `.then` without arguments
// This `.finally` waits until the HTTP request is done
logPromise("foo->one: ", someFunction({id: 1, someValue: 'foo'})) // sets 'stage' to 'one'
.finally(function(){
debugger;
logPromise("bar->two: ", someFunction({id: 1, someValue: 'bar'})); // NOT setting 'stage' to 'two'
});
});
});
If it is not apparent already, do not copy the above snippet into your code. It will break your code because the above snippet is rigged with dummy Node modules designed to produce set results. Instead, copy each individual file (each wrapped in a function) from the snippet above into the corresponding file of your code if you must copy. Also, while copying, keep in mind not to copy the dummy stuff above the blatant IGNORE ALL CODE ABOVE THIS LINE indicator. Also, do not forget to test rigorously. I am much more familiar with browser JavaScript than Node JavaScript, so it is possible (though unlikely) that I may have introduced a potential source of errors.
someObject[inputObject.id] = objectForThis; is not needed
I could give you a super concise quick answer to this question. However, I feel that the "quick" answer would not do you justice because this particular question requires a far greater depth of understanding to be able come up with an explanation than to simply read someone else's explanation. Further, this is a very crucial concept in Javascript. Thus, it is very necessary to be able to come up with the explanation on your own so that you do not run into the same trouble again 5 minutes from now. Thus I have written the below tutorial to guide you to the answer so that you can be sure that you have complete understanding.
In Javascript, there are expressions such as 7 - 4 which yields 3. Each expression returns a value that may be used by further expressions. 3 * (4 + 1) first evaluates 4 + 1 into 3 * 5, then that yields 15. Assignment expressions (=,+=,-=,*=,/=,%=,**=,&=,|=, and ^=) only change the left-hand variable. Any right-hand variable stays the exact same and contains the same value:
var p = {};
var ptr = p;
// Checkpoint A:
console.log('Checkpoint A: p === ptr is now ' + (p === ptr));
ptr = null;
// Checkpoint B:
console.log('Checkpoint B: p === ptr is now ' + (p === ptr));
Let us examine what p and ptr look like at Checkpoint A.
As seen in the above diagram, both p and ptr are kept separate even though they both point to the same object. Thus, at Checkpoint B, changing ptr to a different value does not change p to a different value. At Checkpoint B, variable p has remained unchanged while ptr is now null.
At Checkpoint A, keep in mind that (although p and ptr are distinct variables with different memory addresses) p and ptr both point to the same object because the location in memory they point to is the same index number. Thus, if we changed this object at checkpoint A, then reading/writing the properties of p would be the same as reading/writing the properties of ptr because you are changing the data being pointed to, not which variable is pointing to what data.
function visualize(inputObject) {
// displays an object as a human-readable JSON string
return JSON.stringify(inputObject);
}
var p = {};
p.hello = "world";
// Checkpoint 0:
console.log('Checkpoint 0: p is ' + visualize(p));
var ptr = p;
ptr.foo = "bar";
// Checkpoint A:
console.log('Checkpoint A: p is ' + visualize(p) + ' while ptr is ' + visualize(ptr));
ptr = null;
// Checkpoint B:
console.log('Checkpoint B: p is ' + visualize(p) + ' while ptr is ' + visualize(ptr));
p.foo = p.hello;
// Checkpoint C:
console.log('Checkpoint C: p is ' + visualize(p) + ' while ptr is ' + visualize(ptr));
As seen at Checkpoint A above, changing p is the same as changing ptr. What about when we reassign an object? The old object which is no longer pointed to get cleaned up by tan automatic Garbage Collector.
function visualize(inputObject) {
// displays an object as a human-readable JSON string
return JSON.stringify(inputObject);
}
var first = {one: "is first"};
first = {uno: "es el primero"};
var second = {two: "is second"};
second = first;
console.log("second is " + JSON.stringify(second));
Function arguments are the same as variables in this respect.
var obj = {};
setVariable(obj);
obj.key = "value";
console.log("obj is " + JSON.stringify(obj));
function setVariable(inputVariable){
inputVariable.setValue = "set variable value";
inputVariable = null;
}
Is the same as:
var obj = {};
/*function setVariable(*/ var inputVariable = obj; /*){*/
inputVariable.setValue = "set variable value";
inputVariable = null;
/*}*/
obj.key = "value";
console.log("obj is " + JSON.stringify(obj));
Objects are also no different:
function visualize(inputObject) {
// displays an object as a human-readable JSON string
return JSON.stringify(inputObject);
}
var variables = {};
var aliasVars = variables;
// Now, `variables` points to the same object as `aliasVars`
variables.p = {};
aliasVars.p.hello = "world";
// Checkpoint 0:
console.log('Checkpoint 0: variables are ' + visualize(variables));
console.log('Checkpoint 0: aliasVars are ' + visualize(aliasVars));
variables.ptr = aliasVars.p;
aliasVars.ptr.foo = "bar";
// Checkpoint A:
console.log('Checkpoint A: variables are ' + visualize(variables));
console.log('Checkpoint A: aliasVars are ' + visualize(aliasVars));
variables.ptr = null;
// Checkpoint B:
console.log('Checkpoint B: variables are ' + visualize(variables));
console.log('Checkpoint B: aliasVars are ' + visualize(aliasVars));
aliasVars.p.foo = variables.p.hello;
// Checkpoint C:
console.log('Checkpoint C: variables are ' + visualize(variables));
console.log('Checkpoint C: aliasVars are ' + visualize(aliasVars));
Next is object notation just to make sure that we are on the same page.
var obj = {};
obj.one = 1;
obj.two = 2;
console.log( "obj is " + JSON.stringify(obj) );
is the same as
var obj = {one: 1, two: 2};
console.log( "obj is " + JSON.stringify(obj) );
is the same as
console.log( "obj is " + JSON.stringify({one: 1, two: 2}) );
is the same as
console.log( "obj is {\"one\":1,\"two\":2}" );
The hasOwnProperty allows us to determine whether or not an object has a property.
var obj = {};
// Checkpoint A:
console.log("Checkpoint A: obj.hasOwnProperty(\"hello\") is " + obj.hasOwnProperty("hello"));
console.log("Checkpoint A: obj[\"hello\"] is " + obj["hello"]);
obj.hello = "world"; // now set the variable
// Checkpoint B:
console.log("Checkpoint B: obj.hasOwnProperty(\"hello\") is " + obj.hasOwnProperty("hello"));
console.log("Checkpoint B: obj[\"hello\"] is " + obj["hello"]);
obj.hello = undefined;
// Checkpoint C:
console.log("Checkpoint C: obj.hasOwnProperty(\"hello\") is " + obj.hasOwnProperty("hello"));
console.log("Checkpoint C: obj[\"hello\"] is " + obj["hello"]);
delete obj.hello;
// Checkpoint D:
console.log("Checkpoint D: obj.hasOwnProperty(\"hello\") is " + obj.hasOwnProperty("hello"));
console.log("Checkpoint D: obj[\"hello\"] is " + obj["hello"]);
Prototypes in javascript are crucial to understanding hasOwnProperty and work as follows: when a property is not found in an object, the object's __proto__ is checked for the object. When the object's __proto__ does not have the property, the object's __proto__'s __proto__ is checked for the property. Only after an object without a __proto__ is reached does the browser assume that the wanted property does not exist. The __proto__ is visualized below.
var obj = {};
console.log('A: obj.hasOwnProperty("foo") is ' + obj.hasOwnProperty("foo"));
console.log('A: obj.foo is ' + obj.foo);
obj.__proto__ = {
foo: 'value first'
};
console.log('B: obj.hasOwnProperty("foo") is ' + obj.hasOwnProperty("foo"));
console.log('B: obj.foo is ' + obj.foo);
obj.foo = 'value second';
console.log('C: obj.hasOwnProperty("foo") is ' + obj.hasOwnProperty("foo"));
console.log('C: obj.foo is ' + obj.foo);
delete obj.foo;
console.log('D: obj.hasOwnProperty("foo") is ' + obj.hasOwnProperty("foo"));
console.log('D: obj.foo is ' + obj.foo);
delete obj.__proto__.foo;
console.log('E: obj.hasOwnProperty("foo") is ' + obj.hasOwnProperty("foo"));
console.log('E: obj.foo is ' + obj.foo);
Infact, we could even store a reference to the __proto__ and share this reference between objects.
var dog = {noise: "barks"};
var cat = {noise: "meow"};
var mammal = {animal: true};
dog.__proto__ = mammal;
cat.__proto__ = mammal;
console.log("dog.noise is " + dog.noise);
console.log("dog.animal is " + dog.animal);
dog.wagsTail = true;
cat.clawsSofa = true;
mammal.domesticated = true;
console.log("dog.wagsTail is " + dog.wagsTail);
console.log("dog.clawsSofa is " + dog.clawsSofa);
console.log("dog.domesticated is " + dog.domesticated);
console.log("cat.wagsTail is " + cat.wagsTail);
console.log("cat.clawsSofa is " + cat.clawsSofa);
console.log("cat.domesticated is " + cat.domesticated);
However, the above syntax is terribly illperformant because it is bad to change the __proto__ after the object has been created. Thus, the solution is to set the __proto__ along with the creation of the object. This is called a constructor.
function Mammal(){}
// Notice how Mammal is a function, so you must do Mammal.prototype
Mammal.prototype.animal = true;
var dog = new Mammal();
// Notice how dog is an instance object of Mammal, so do NOT do dog.prototype
dog.noise = "bark";
var cat = new Mammal();
cat.noise = "meow";
console.log("dog.__proto__ is Mammal is " + (dog.__proto__===Mammal));
console.log("cat.__proto__ is Mammal is " + (cat.__proto__===Mammal));
console.log("dog.__proto__ is Mammal.prototype is " + (dog.__proto__===Mammal.prototype));
console.log("cat.__proto__ is Mammal.prototype is " + (cat.__proto__===Mammal.prototype));
console.log("dog.noise is " + dog.noise);
console.log("dog.animal is " + dog.animal);
dog.wagsTail = true;
cat.clawsSofa = true;
Mammal.prototype.domesticated = true;
console.log("dog.wagsTail is " + dog.wagsTail);
console.log("dog.clawsSofa is " + dog.clawsSofa);
console.log("dog.domesticated is " + dog.domesticated);
console.log("cat.wagsTail is " + cat.wagsTail);
console.log("cat.clawsSofa is " + cat.clawsSofa);
console.log("cat.domesticated is " + cat.domesticated);
Next, the this object in Javascript is not a reference to the instance like in Java. Rather, the this in Javascript refers to the object in the expression object.property() when functions are called this way. When functions are not called this way, the this object refers to undefined in strict mode or window in loose mode.
"use strict"; // "use strict"; at the VERY top of the file ensures strict mode
function logThis(title){
console.log(title + "`this === undefined` as " + (this === undefined));
if (this !== undefined) console.log(title + "`this.example_random_name` as " + this.example_random_name);
}
logThis.example_random_name = "log this raw function";
logThis("logThis() has ");
var wrapper = {logThis: logThis, example_random_name: "wrapper around logThis"};
wrapper.logThis("wrapper.logThis has ");
var outer = {wrapper: wrapper, example_random_name: "outer wrap arounde"};
outer.wrapper.logThis("outer.wrapper.logThis has ");
We can finally take all this knowledge and extrapolate it out to the real example.
var someObject = {};
function someFunction(inputObject) {
if (!someObject.hasOwnProperty(inputObject.id)) {
someObject[inputObject.id] = {};
someObject[inputObject.id].contact = {};
}
// get object
var objectForThis = someObject[inputObject.id];
objectForThis.stage = inputObject.stage;
}
var setTo = {};
setTo.id = 1;
setTo.stage = "first stage";
someFunction(setTo);
console.log("someObject is " + JSON.stringify(someObject));
First, let us inline the function and the setTo
var someObject = {};
var setTo = {id: 1, stage: "first stage"};
/*function someFunction(*/ var inputObject = setTo; /*) {*/
if (!someObject.hasOwnProperty(inputObject.id)) {
someObject[inputObject.id] = {};
someObject[inputObject.id].contact = {};
}
// get object
var objectForThis = someObject[inputObject.id];
objectForThis.stage = inputObject.stage;
/*}*/
console.log("someObject is " + JSON.stringify(someObject));
Next, let us inline the setTo object.
var someObject = {};
var setTo = {id: 1, stage: "first stage"};
if (!someObject.hasOwnProperty(setTo.id)) {
someObject[setTo.id] = {};
someObject[setTo.id].contact = {};
}
// get object
var objectForThis = someObject[setTo.id];
objectForThis.stage = setTo.stage;
console.log("someObject is " + JSON.stringify(someObject));
Then:
var someObject = {};
if (!someObject.hasOwnProperty(1)) {
someObject[1] = {};
someObject[1].contact = {};
}
// get object
var objectForThis = someObject[1];
objectForThis.stage = "first stage";
console.log("someObject is " + JSON.stringify(someObject));
To visually demonstrate the pointers:
var someObject = {};
if (!someObject.hasOwnProperty(1)) {
var createdObject = {};
someObject[1] = createdObject;
someObject[1].contact = {};
}
// get object
var objectForThis = someObject[1];
console.log("createdObject === objectForThis is " + (createdObject === objectForThis));
objectForThis.stage = "first stage";
console.log("someObject is " + JSON.stringify(someObject));
Please tell me if you have any further questions.
If you are just glancing at this, then please don't forget to read the full article above. I promise you that my article above is shorter and tries to go deeper into Javascript than any other article you may find elsewhere on the internet.
No, your someOtherFunction should not use new Promise. You should just chain onto the status.getApiTwo('foo') call, or create immediately resolved promises using Promise.resolve. That way it will always return a promise like your call in someFunction expects it.
function someOtherFunction(stage) {
switch (stage) {
case 'zero':
// some code
return Promise.resolve('one');
case 'one':
return status.getApiTwo('foo').then(response => {
// ^^^^^^
console.log(response);
return 'two';
// ^^^^^^
});
default:
// do nothing
return Promise.resolve(undefined);
}
}
Alternatively you could use async/await:
async function someOtherFunction(stage) {
switch (stage) {
case 'zero':
// some code
return 'one';
case 'one':
const response = await status.getApiTwo('foo');
console.log(response);
return 'two';
default:
// do nothing
break;
}
}

JavaScript Callback Inner Function not Executed

I have a client-side web-application that takes a csv-file, parses it into various data types, searches for something specific, and displays a table with the answer on the screen. The search function returning a null string. This occurs because its search parameter, returned by a callback function and put into lib, returns null.
I'm fairly certain this is a callback issue, but I've messed around with the order so much I'm not sure what goes where anymore in my html...A second set of eyes would be appreciated.
The desired series of events
fileToArray() gives us an array
search() looks in the array for its specified item and returns a csv-format string containing what it found
displayTable takes that csv-format string and outputs it to the desired location
The Code
// jQuery call to fetch the client-side csv file - this works when called by itself.
const fileToArray = () => {
console.log("fileToArray started.");
$.get({
url: CSV_LOCATION,
dataType: "text",
success: function (result) {
console.log("splitting result by newline...");
let csvLines = result.split("\n");
console.log("split successful. generating array into retval ...");
let retval = [];
for (let i = 0; i < csvLines.length; i++) {
// [0][0] is number [0][1] is class, [0][2] is unit, [0][3] is lesson
retval[i] = csvLines[i].split(",");
}
console.log("success! Returning retval.");
return retval;
// callback(result);
// return result;
},
failure: function (xhr, status, error) {
console.log("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
alert("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
}
})
};
// PRECONDITION: form is #search-params in index.js
// > lib is the result of fileToArray()
// POSTCONDITION: result is a csv-format string to be passed to displayTable() in index.js
const search = (form, callback) => {
console.log("search called...");
// vvvvv The probable root of the problem vvvvv //
let lib = callback;
console.log(lib.length + " is lib's length.");
let result = "";
console.log("search nested for loop called...");
for (let i = 0; i < lib.length; i++) {
// check class
console.log("checking class " + form.class.value + "...");
if (lib[i][1] === form.class.value) {
// check unit
console.log("checking unit " + form.unit.value + "...");
if (Number(lib[i][2]) === Number(form.unit.value)) {
console.log("adding to result...");
result += lib[i] + "\n";
}
}
}
console.log("search success! result: " + result.length + " characters");
console.log(result);
return result;
};
<!-- I'm almost 100% certain I've messed up the callback in this button,
but I still don't quite understand how... I've played with
displayTable(fileToArray(search(...))), but I don't quite know how it should go -->
<button class="btn btn-primary"
onclick="displayTable(search(document.getElementById('search-params'), fileToArray), $('#card-display'))">
Submit
</button>
What I've tried
I have looked to the following sites for inspiration (none have helped):
JavaScript is Sexy
JavaScript: Passing parameters to a callback function
JavaScript Callback Functions
Passing arguments to callback functions
In Summary
It's painfully obvious I still don't understand callbacks fully. Any help would be appreciated.
You could use async / await
const displayTable = async () => {
let arrayFromFile = await fileToArray(); // fileToArray executes and assigns the returned value when it completes
let searchedData = search(form, arrayFromFile);
// Display the table
};
Thanks to #kapantzak for the inspiration!! Turns out, I was using callbacks horribly bass-ackwards. According to this, the old-school async style is something akin to
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
So, the relevant code now looks like this:
const fileToArray = (callback) => {
// console.log("fileToArray started.");
$.get({
url: CSV_LOCATION,
dataType: "text",
success: function (result) {
let csvLines = result.split("\n");
let retVal = [];
for (let i = 0; i < csvLines.length; i++) {
// [0][0] is number [0][1] is class, [0][2] is unit, [0][3] is lesson
retVal[i] = csvLines[i].split(",");
}
callback(retVal);
},
failure: function (xhr, status, error) {
console.log("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
alert("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
}
})
};
// =======
const search = (form, lib, callback) => {
let result = "";
let formClass = form.class.value.toLowerCase();
let formUnit = form.unit.value.toLowerCase();
let formLesson = form.lesson.value.toLowerCase();
for (let i = 0; i < lib.length; i++) {
// check class
if (lib[i][1].toLowerCase() === formClass) {
// check unit
if (Number(lib[i][2].toLowerCase()) === Number(formUnit)) {
result += lib[i] + "\n";
}
}
}
console.log(result);
callback(result);
};
<button class="btn btn-primary"
onclick="fileToArray(function(result) {
search(document.getElementById('search-params'), result, function(newResult) {
displayTable(newResult, $('#card-display'));
});
});">
Submit
</button>
This righted the wrongs and caused my search and display to function properly.

Accessing array / scope of variable [duplicate]

This question already has answers here:
How to wait until jQuery ajax request finishes in a loop?
(5 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 7 years ago.
I am attempting to populate an array and use it later. I am getting "undefined" result when I try accessing objects at indexes.
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
var result = {};
streamers.forEach(function(stream)
{
$.getJSON(url + 'streams/' + stream + cb).success(function(data)
{
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
});
});
alert(result[0]);
alert(result[1]);
alert(result[2]);
});
What you need is a callback, as your getting something for the server mostly happens in asynchronous processes, which mean that the code continues executing and only adds something to the array when something is returned. You want the script to only alert things when it completed a task, which is why we call it a call back, as it will call the passed function back (aka when it's done).
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
// This should be initialised as an array, not an object!
var result = [];
function callback(){
// I add this line to reduce the amount of alerts
if(result.length !== 3) return;
alert(result[0]);
alert(result[1]);
alert(result[2]);
}
streamers.forEach(function(stream){
$.getJSON(url + 'streams/' + stream + cb).success(function(data){
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
callback();
});
});
});
You are accessing result before the $.getJSON callbacks have been executed. Many questions like this have been answered already, see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference for an explanation of the problem.
You should make use of the fact that all Ajax methods return promises/deferred objects and use $.when to wait for all of them to complete:
var deferreds = streamers.map(function(stream) {
return $.getJSON(url + 'streams/' + stream + cb).then(function(data) {
var streaming = (data.stream === null) ? false : true;
return stream + " - " + streaming;
});
});
$.when.apply(null, deferreds).then(function(result) {
console.log(result);
});
$.getJSON is asynchronous.
Your alerts are running before you have data in the array.
To view the data when it's available, move the alerts (or better yet, console.logs) up into the success callback:
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
var result = {};
streamers.forEach(function(stream) {
$.getJSON(url + 'streams/' + stream + cb).success(function(data) {
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
console.log(result);
});
});
});
Moving from looking at the results to using the results, you'll want to break things up a bit:
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
var result = {};
var getStreams = streams.map(function (stream) {
return $.getJSON(url + 'streams/' + stream + cb).success(function(data) {
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
console.log(result);
});
});
var onResultsSuccess = function (results) {
console.log("I have all my streams, let's take a look at them:", results);
};
var onResultsFail = function () {
console.log("Something's up!", arguments);
};
$.when(getStreams).then(onResultsSuccess, onResultsFail);
});
Untested so assume it's pseudo!
Not only should you defer the alert to after the calls, you should not loop Ajax externally..
Here is what I would suggest
function getStream() {
if (result.length >= streamers.length) return;
var stream = streamers[result.length];
$.getJSON(url + 'streams/' + stream + cb).success(function(data){
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
getStream();
});
}
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
// This should be initialised as an array, not an object!
var result = [];
$(function() {
getStream();
});

How to use Promise to return value from assync Angular function into calling method?

I have list which is filled in for cycle where I calling assync function by thos way:
In For cycle I'm calling
row.SUCCES_RATE_SINCE = $scope.computeSuccessRateSinceStart(row);
Called function
// Calculate percentage of a whole from since
$scope.computeSuccessRateSinceStart = function(row) {
db = window.sqlitePlugin.openDatabase({name:"callplanner"});
// GET APPT COUNT
db.transaction(function(tx) {
tx.executeSql(sqlQuery, [], function(tx,results){
// init empty array for results
for (var i=0; i < results.rows.length; i++){
row = results.rows.item(i);
//Udpate date for writeout
//row.DATE = moment(row.DATE).format('ddd DD.M');
console.log("row APPT count is " + JSON.stringify(row));
apptCnt = row.APPT_CNT;
convCnt = row.CONVERS_CNT;
dailySuccessRateSince = apptCnt / convCnt * 100;
console.log("Success rate since is " +dailySuccessRateSince);
// THIS IS NOT WORKING
return Math.round(dailySuccessRateSince);
}
});
},function (e) {
console.log("ERROR: " + e.message);
$ionicLoading.show({
template: $translate.instant('ERROR_DATABASE'),
duration:1000
});
});
};
Problem is that computed value is always returned null (return function is executed before value is available in scope).
I'm quite new in Angular but i found that this issue could be solved using promises. Could somebody give me the example how to return value properly?
Many thanks for any help.
EDIT:
Called method is now triggered, but i cannot pass returned value into variable like this:
var test = $scope.computeSuccessRateSinceStart(row).then(function(result){
//ALERT WITH VALUE WORKS FINE
alert("Result " + result);
return result;
});
// THIS GIVES ME EMPTY ARRAY {}
alert("Result " + JSON.stringify(test));
Why don't you just make your method such that it always returns a promise, and then extract the result from the promise?
$scope.computeSuccessRateSinceStart = function(row) {
var deferred = $q.defer();
db = window.sqlitePlugin.openDatabase({name:"callplanner"});
// GET APPT COUNT
db.transaction(function(tx) {
tx.executeSql(sqlQuery, [], function(tx,results){
// init empty array for results
for (var i=0; i < results.rows.length; i++){
row = results.rows.item(i);
//Udpate date for writeout
//row.DATE = moment(row.DATE).format('ddd DD.M');
console.log("row APPT count is " + JSON.stringify(row));
apptCnt = row.APPT_CNT;
convCnt = row.CONVERS_CNT;
dailySuccessRateSince = apptCnt / convCnt * 100;
console.log("Success rate since is " +dailySuccessRateSince);
// THIS IS NOW WORKING:
deferred.resolve(Math.round(dailySuccessRateSince));
}
});
}, function(e) {
console.log("ERROR: " + e.message);
deferred.reject(e);
});
return deferred.promise;
};
Usage:
$scope.computeSuccessRateSinceStart(row).then(function(result){
// THIS GIVES THE VALUE:
alert("Result " + JSON.stringify(test));
return result;
}, function(e)
$ionicLoading.show({
template: $translate.instant('ERROR_DATABASE'),
duration:1000
});
});

Categories