Node.js Promise within Promise within Promise - javascript

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;
}
}

Related

Node.js Function returning undefined? [duplicate]

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")

Can I eval expression in object context?

Good day. I need to eval expression in some object context, but the only solution I found is to create stubs for every object function:
var c = {
a : function () {
return 'a';
},
b : function () {
return 'b';
}
};
function evalInObj(c, js) {
function a() {
return c.a();
}
function b() {
return c.b();
}
return eval(js);
};
console.log(evalInObj(c, 'a() + b()'));
Show me the right way, please. Can I do it with prototype?
var C = function(id) {
this.id = id;
}
C.prototype.a = function () {
return 'a' + this.id;
}
C.prototype.b = function () {
return 'b' + this.id;
}
function evalCtx(js) {
console.log(this); // C {id: 1}
return eval(js);
}
var c1 = new C(1);
evalCtx.call(c1, 'a() + b()'); // error: a is not defined
For late-comers who are still looking for the solution of the issue (or the similar). Instead of using eval(), we can create anonymous function dynamically and evaluate the expression in the object context:
function evaluate(expression, context = {}) {
try {
// console.debug("[DEBUG] Dynamic anonymous function to be defined:\n%s", `function(${[...Object.keys(context)].join()}) {\n'use strict'; return (${expression})\n}`)
const fun = Function(...Object.keys(context), `'use strict'; return (${expression})`)
// console.debug("[DEBUG] Dynamically defined anonymous function:\n%o", fun)
const result = fun(...Object.values(context))
// console.debug("[DEBUG] Evaluation result: %o", result)
return result
} catch(error) {
if(error.message === `Unexpected token ')'`) throw SyntaxError('Unexpected token, likely at the end of expression.')
else throw error
}
}
To assert:
console.assert(evaluate('a===1 && b===2', {a: 1, b: 2}) === true)
console.assert(evaluate('a===1 && b===3', {a: 1, b: 2}) === false)
console.assert(evaluate('f()', {a: 1, f: ()=>11}) === 11)
(() =>
{
// 'use strict';
function run(expression, context = {})
{
return function ()
{
return eval(expression);
}.call(context);
}
let context = {a:{b:'Bb'}};
console.log(run('this', context)); // {a:{b:'Bb'}}
console.log(run('this.a', context)); // {b:'Bb'}
console.log(run('this.a.b', context)); // 'Bb'
console.log(run('a.b', context)); // ReferenceError: a is not defined
})();
The most notable advantage of this technique is that it work without the with keyword,
Thus even in strict mode
+function()
{
// jsut pollyfills for backward browsers...
Object.prototype.keys || (Object.defineProperty(Object.prototype, 'keys', {value: function ()
{
var result = []; for (var key in this) result.push(key); return result;
}}));
Object.prototype.entries || (Object.defineProperty(Object.prototype, 'entries', {value: function ()
{
var result = []; for (var key in this) result.push([key, this[key]]); return result;
}}));
// here the magic...
function run(expression, context)
{
var variables = {};
(context instanceof Object) && context.entries().forEach(function(entry)
{
entry[0].match(/^[a-z_$][a-z0-9_$]*$/) && (variables[entry[0]] = entry[1]);
});
return (new Function('return function(' + variables.keys().join(', ') + ') { return ' + expression + '; }'))()// first get the synthetic function
.apply(context, variables.entries().map(function(entry) { return entry[1]; }));
}
var output = run("a + '#' + b", {a: 'Aa', b: 'Bb', 0: 'Zero'});
console.log(output); // Aa#Bb
}();
function runScript(ctx, js){ with(ctx){ return eval(js); }}
closed. thanks all

Throw error when attempting to access non-existent property

I have an object like:
const foo = {
bar: 'bar'
};
I would like to modify it such that if someone tried to access a non-existent property an error would be thrown rather than returning undefined.
For example,
const baz = foo.baz;
// Error: Property 'baz' does not exist on object 'foo'
Is this possible?
With ECMAScript 6 you can use proxies.
var original = {"foo": "bar"};
var proxy = new Proxy(original, {
get: function(target, name, receiver) {
console.log("Name of requested property: " + name);
var rv = target[name];
if (rv === undefined) {
console.log("There is no such thing as " + name + ".")
rv = "Whatever you like"
}
return rv;
}
});
console.log("original.foo = " + proxy.foo); // "bar"
console.log("proxy.foo = " + proxy.whatever); // "Whatever you like"
https://jsfiddle.net/u5b3wx9w/

JSON.parse(), reconnecting to nested objects

I'm having trouble converting JSON to Javascript objects when the JSON data has nested objects. The top level 'Person' object gets recreated fine, but the 'Residence' object property does not
function Person(first, last) {
this.FirstName = first;
this.LastName = last;
this.Residence = {};
}
Person.Revive = function (data) {
return new Person(data.FirstName, data.LastName);
}
Object.defineProperty(Person.prototype, "FullName", {
get: function() { return this.FirstName + " " + this.LastName; }
});
Person.prototype.toJSON = function () {
this.__class__ = "Person";
return this;
});
function Residence(lat, long) {
this.Latitude = lat;
this.Longitude = long;
}
Residence.prototype.toJSON = function () {
this.__class__ = "Residence";
return this;
}
Residence.Revive = function (data) {
return new Residence(data.Latitude, data.Longitude);
}
Object.defineProperty(Residence.prototype, "Location", {
get: function () { return this.Latitude + ", " + this.Longitude; }
});
var p = new Person("Foo", "Bar");
p.Residence = new Residence(44, 33);
console.log("Full name = " + p.FullName);
console.log("Location = " + p.Residence.Location);
var serialization = JSON.stringify(p);
console.log(serialization);
var rawObj = JSON.parse(serialization, function (key, value) {
if (value instanceof Object && value.__class__ == 'Person') {
return Person.Revive(value);
}
if (value instanceof Object && value.__class__ == 'Residence') {
return Residence.Revive(value);
}
return value;
});
console.log("Full name = " + rawObj.FullName);
console.log("Location = " + rawObj.Residence.Location);
The JSON.parse function does get a key/value pair for the 'Residence' object, and a new Residence object is created and returned. However, the resulting 'rawObj.Residence' is just an empty object. Can anyone point out what I'm doing wrong?
The console output is as follows:
Full name = Foo Bar
Location = 44, 33
{"FirstName":"Foo","LastName":"Bar","Age":22,"Residence":{"Latitude":44,"Longitude":33,"__class__":"Residence"},"__class__":"Person"}
Full name = Foo Bar
Location = undefined
Fiddle: http://jsfiddle.net/CadGuy/yyq4dqtx/
var p = new Person("Foo", "Bar");
p.Residence = new Residence(44, 33);
Well, if you are constructing your Person objects like that, you'll have to revive them like this as well:
Person.Revive = function (data) {
var p = new Person(data.FirstName, data.LastName);
p.Residence = data.Residence;
return p;
};
Of course, it might be a good idea to make the residence an (optional?) parameter to Person in the first place.

How can I convert this to be an extension of the Array Prototype?

http://jsfiddle.net/ryanneufeld/Y8ZNU/
With this example I have created a queue modeled after how I assume google is handling analytics events. The thing is I'd like to convert it to be an extension of the array prototype.
What I'm trying to accomplish is that when you create a new instance of Queue and pass in a queue array, the new instance would act as an array with the extra functions I've added.
might not be perfect but it get the job done: (see the link provided by #Pointy in the comments for a good explanation as to what the pitfalls are)
function pseudoArray(name) {
if (!(this instanceof pseudoArray)) {
return new pseudoArray(name);
}
var self = this;
self.name = name || 'defaultName';
var _push = self.push;
self.push = function(args) {
console.log('"' + name + '" pushing [ ' + Array.prototype.slice.apply(arguments) + ' ]');
_push.apply(self, arguments);
};
return self;
}
pseudoArray.prototype = [];
var x = new pseudoArray('fake array');
x.push('yay', 77, function() { alert('yup'); });
x.push('things');
x.push(12);
console.log(x instanceof Array);
console.log('to string: ' + x);
console.log('length: ' + x.length);
console.log('pop result: ' + x.pop());
console.log('length: ' + x.length);
function log() {
/* workaround for chrome not playing nice and letting me .apply to console */
console.log.apply(console, arguments);
}
var q = q || [[log, 'first item q1']],
q2 = q2 || [[log, 'first time q2']];
// You'll want a console open for this.
function Queue(_q, name) {
var _q = _q || [],
name = name || 'mlQueue';
function processQueue() {
task = _q.shift();
while (task) {
func = task.shift();
func.apply(window, task);
task = _q.shift();
}
}
function init() {
_q._push = _q.push;
processQueue();
_q.push = function() {
//first push it to the array
_q._push.apply(_q, arguments);
processQueue();
};
}
function push() {
console.log(name + ' pushing values');
_q.push.apply(_q, arguments);
};
return {
init: init,
push: push,
run: processQueue,
name: name
}
};
var q = new Queue(q, 'q1');
q.push([log, 'q1 and more']);
q.init();
q.push([log, 'q1 and more']);
var q2 = new Queue(q2, 'q2');
q2.init();
q2.push([log, 'q2 and more']);​

Categories