I'm currently using the prompt package in Node to get user input on the command line, however, I would like to return a variable from prompt.get - how would I store the return variable/where can I access it from? I'm relatively new to Node.
const prompt = require('prompt');
prompt.start();
prompt.get(['num'], function (err: any, result: any) {
if (err) { return Error(err); }
if(result.num > 0) {
return "Positive";
}
else {
return "Negative";
}
});
Cheers!
You could initialize the variable outside the callback, and then set the variable inside of the callback:
let value;
prompt.get(['num'], function (err: any, result: any) {
if (err) { return Error(err); }
value = result > 0 ? "Positive" : "Negative";
});
But the problem is the value will be empty until it's done, so if you try to call it right after, it will be empty.
let value;
prompt.get(['num'], function (err: any, result: any) {
if (err) { return Error(err); }
value = result > 0 ? "Positive" : "Negative";
});
console.log(value); // => undefined
The package also supports promises though, so you can just use await, as shown on their readme:
(async () => {
const { num } = await prompt.get(['num']);
})();
You should notice the anonymous self executing async function, that's because await only works inside of async functions (technically not needed after node v15 & while using modules).
Related
I have a div with the following html
<div data-cy="pop" style="margin-right:5px;">12,300</div>
I am trying to get 12,3001 convert it to a int and save the value for use in another function. I am getting this error.
cy.then() failed because you are mixing up async and sync code.The value you synchronously returned was: 12300
Here is my code to get the value.
cy.elem('pop').invoke('text').then((num) => {
const res = parseInt(num.replaceAll(',',''))
return res
})
Does anyone know what I'm doing wrong?
It's not clear how you are attempting to pass the value of your parsed int into the another function.
One way to save the value of something is with an alias. To do that you will need to use the .as().
cy.elem('pop')
.invoke('text')
.then((num) => { return parseInt(num.replaceAll(',','')) })
.as('num')
// later down in your code
cy.get('#num')
.then(number => {
functionYouWantToPassYourNumber(number)
})
Bit of an edge-case, TLDR: return cy.wrap(res).
If I run this as a minimal test for your code, it passes
const num = "12,300";
cy.wrap(num).then((numAsString) => {
const numAsInt = parseInt(numAsString.replace(",", ""));
return numAsInt
})
.then(num => {
cy.log(num) // logs 12300 ✅
})
If I add an async line cy.wait(5000) (for example), it fails
const num = "12,300";
cy.wrap(num).then((numAsString) => {
const numAsInt = parseInt(numAsString.replace(",", ""));
cy.wait(5000)
return numAsInt
})
.then(num => {
cy.log(num) ❌
})
If I then cy.wrap() the result, it passes again
const num = "12,300";
cy.wrap(num).then((numAsString) => {
const numAsInt = parseInt(numAsString.replace(",", ""));
cy.wait(5000)
return cy.wrap(numAsInt)
})
.then(num => {
cy.log(num) // logs 12300 ✅
})
Theoretically your code should pass, but if you have another command inside the .then() that could be causing it.
Or possible the cy.elem('pop') is causing it.
For reference, this is Cypress' own test for the error
describe("errors", {defaultCommandTimeout: 100}, () => {
beforeEach(function () {
this.logs = [];
cy.on("log:added", (attrs, log) => {
this.lastLog = log;
this.logs?.push(log);
});
return null;
});
it("throws when mixing up async + sync return values", function (done) {
cy.on("fail", (err) => {
const { lastLog } = this;
assertLogLength(this.logs, 1)
expect(lastLog.get("error")).to.eq(err);
expect(err.message).to.include(
"`cy.then()` failed because you are mixing up async and sync code."
);
done();
});
cy.then(() => {
cy.wait(5000);
return "foo";
});
});
});
I have an async function in a class that has a try-catch. When the try encounters an error, I would like to recall the function to reattempt.
My current implementation successfully recalls the function in the catch block, but if the sequential attempts succeed, the returned result is always undefined.
Code:
class Extractor {
constructor(item) {
this.item = item
}
async start() {
try {
let results = await someApiCallPromise()
this.item.moreData = results
return this.item //<== Upon sequential recall from catch, it always return undefined
} catch (err) {
if (err == "someError") {
await this.start() // <== this.start() recalls successfully
} else {
//Other error handling method
}
}
}
}
//Usage
let item = {
param1: '1',
param2: '2'
}
let extractor = new Extractor(item)
extractor.start()
.then(item => {
console.log(item) // <== Always return undefined if recalled from catch block
})
How do I return this.item if it suceeds from the recall in the catch block?
You must return your recursive call in catch:
return this.start()
Since you are returning a promise (call to async function) there is no need to await on that.
I'm exporting/importing an empty array to implement MVC patern and I was able to successfully store data in it by calling the allMentors method on the Mentor class.
Secondly, I want to be DRY and get the data from the array and then use it to find a specific mentor but I'm getting an empty array.
I searched the internet and I followed the example of using this keyword and call the static method inside another but NodeJS is throwing an error that this is undefined.
ALL MENTORS method
class Mentor{
static async allMentors(req, res) {
try {
users.forEach(user => {
if(user.is_mentor === true) {
mentors.push(user);
}
})
const ObjKeyRename = (src, map) => {
const dst = {};
for (const key in src) {
if (key in map)
// rename key
dst[map[key]] = src[key];
else
// same key
dst[key] = src[key];
}
return dst;
};
const uniqueMentors = Array.from(new Set(mentors.map(m => m.id)))
.map(id => {
return new Promise((resolve, reject)=> {
const currMentor = mentors.find(m => m.id === id);
const modMentor = ObjKeyRename(currMentor, { "id": "mentorId" });
return resolve(modMentor);
})
})
Promise.all(uniqueMentors).then(output => {
output.forEach(async obj => {
await delete obj['password'];
})
return res
.status(200)
.json(new ResponseHandler(200, 'All Mentors', output, null).result());
})
} catch (err) {
return res
.status(500)
.json(new ResponseHandler(500, err.message, null).result());
}
}
static async singleMentor(req, res) {
const returnedMentors = this.allMentors;
console.log('THE RETURNED:',returnedMentors)
const theMentor = returnedMentors.find(u => u.mentorId === parseInt(req.params.mentorId));
try {
if (!theMentor) return res
.status(404)
.json(new ResponseHandler(404, `Mentor number ${req.params.mentorId} not found`, null).result());
return res
.status(200)
.json(new ResponseHandler(200, 'Your mentor', theMentor, null).result());
} catch (err) {
return res
.status(500)
.json(new ResponseHandler(500, err.message, null).result())
}
}
}
export default Mentor;
What am I doing wrong? I appreciate your continued help as a JS learner.
There are multiple problems with your code:
a static method needs to be called by Class (Mentor) not by Reference (this)
a async method needs to be awaited or called with a callback (.then())
So for example you have the class MentorHandler:
class Mentor {
constructor() {
}
static async singleMentor(req, res) {
// ... some more code
}
static async allMentors(req, res) {
// await the result of the method
// call by class
const returnedMentors = await Mentor.allMentors();
// ... some more code
}
}
There is a service which gets the data and has a then-catch structure:
getData(siteId) {
const accessToken = JSON.parse(sessionStorage.getItem('ls.authorizationData')).token;
const config = { ...
};
this.canceler.resolve();
this.canceler = this.$q.defer();
config.timeout = this.canceler.promise;
const siteIds = this.MyService.getSitesIds();
return this.$http.get(`${TEST_API_URL}value?siteIds=${siteId || siteIds}`, config)
.then(result => {
return {
data: result.data,
};
}).catch((e) => {
if (e.status === -1 && e.xhrStatus === "abort") {
return Promise.resolve({canceled: true});
}
debugger;
this.hasData = false;
return Promise.reject('Cannot access data ');
});
}
I'm calling this function from the controller, initially in $onInit() like this:
$onInit() {
getData.call(null, this);
}
and as a function:
function getData(MyCtrl) {
MyCtrl.loading = true;
MyCtrl.MyService.getData(MyCtrl.siteId).then(result => {
if (result.canceled) {
return;
}
...
}
This works fine.
The problem appears when I want to send a variable from service to controller if data is not there (if the catch() happens).
I tried to change the return by wrapping the Promise inside an object like
return {promise: Promise.reject('Cannot access data ')};
and add the variable inside this object:
return {promise: Promise.reject('Cannot access data ')
hasData: false};
but it seems that it's not the right way. I need to know in the controller if the data was got or not.
Do you have any suggestions how to solve this?
Normally when you want to return more data from a Promise rejection, you do it the same way you would return from a normal exception, you extend from the Error object.
Here is an example..
class MyError extends Error {
constructor (message, extra) {
super(message);
this.extra = extra;
}
}
//standard promise rejection way.
function badPromise() {
return Promise.reject(
new MyError("Bad Error", "Extra Stuff")
);
}
//works if throw error inside an async function
async function badPromise2() {
throw new MyError("Bad Error2", "Extra Stuff2");
}
async function test() {
try {
if (Math.random() > 0.5) await badPromise();
else await badPromise2();
} catch(e) {
if (e instanceof MyError) {
console.error(`${e.message}, extra = ${e.extra}`);
} else {
console.error(`${e.toString()}`);
}
}
}
test();
ps. This is using new ESNext features, but the same applies if doing ES5..
It might be a bit confusing what I'm asking but I'll try to be as clear as I can.
Basically I'm doing unit test with mocha/chai for my Data Access Layer of my Node.JS server. I'm using bluebird to return a promise and an SQLite Databases.
That's my function insert I want to test :
insert(sqlRequest, sqlParams, sqlRequest2) {
return new Promise(function (resolve, reject) {
let insertStatement = this.getDatabase().prepare(sqlRequest);
let getStatement = this.getDatabase().prepare(sqlRequest2);
insertStatement.run(sqlParams, err => {
console.log('this.changes = ', this.changes);
if (this.changes === 1) {
getStatement.all({ $id: this.lastID }, (err, rows) => {
if (err) {
console.log('entered second err');
reject(err);
} else {
resolve(rows[0]);
}
});
} else {
console.log('entered first err ');
reject(err);
}
});
}.bind(this));
}
And that's my test with mocha :
it('insert : Error 2st SQL query', function (done) {
const daoCommon = new DaoCommon();
daoCommon.getDatabase = () => {
return {
prepare: (sqlRequest) => {
return {
all: (sql, callback) => {
let err = {};
let rows = null;
callback(err, rows);
},
run: (sqlParams, callback) => {
let err = undefined;
callback(err);
}
}
}
}
}
daoCommon.insert('', '', '')
.then(success => {
expect.fail();
})
.catch(error => {
expect(error).to.eql({});
})
.finally(function () {
done();
})
});
I want to simulate a test where the this.changes is equal to 1 but I don't know how/where I can set this value. According to what I've read this this object is from the callback function, but I have no idea exactly from where it comes or how to set it for my tests.
Update:
You can set the this of a function you are calling with .call of the method.
In your case calling callback with this.changes value will look like:
var thisObject = {
changes: 1
};
callback.call(thisObject, err);
This will set the value this.changes of your callback function.
The value of this is explained in the API documentation
If execution was successful, the this object will contain two
properties named lastID and changes which contain the value of the
last inserted row ID and the number of rows affected by this query
respectively.
It means that the callback function will always have this.changes. You can not change it unless you set this.changes = something manually, which I don't understand why would you do that.
Thank for #Maxali comment, I will post the answer below :
You can set this when calling the function callback(err) by using .call(). eg: callback.call({changes:1}, err). this will set changes to 1
And note that I had to change this line insertStatement.run(sqlParams, err => { where I have the callback from an arrow function to a function declaration insertStatement.run(sqlParams, function(err) { for this to work. I assume this is due to the this which in an arrow function doesn't refer to the object inside the function itself.