I have a class which has 10 methods, and only 8 of those 10 should be exported, the other 2 are for authenticating tokens and I don't want them to be exported since the user won't need to call them ever ( I'm writing a package for npmjs ), my question is, how to only export the class with those 8 methods while the class itself is using the other two?
Edit: added some code
class StackClass {
constructor(p1, p2) {
this.data = {
p1,
p2,
accessToken: null,
expiresIn: null,
tokenType: null
};
}
async getToken() {
return new Promise(async (resolve, reject) => {
try {
let fetchResponse = await fetch(
"url with this.data.p1 and this.data.p2"
);
let fetchData = await fetchResponse.json();
if (fetchData.status != 200) {
reject("ERROR");
} else {
// get the token and save it in this.data
this.data.accessToken = fetchData.access_token;
this.data.expiresIn = fetchData.expires_in;
this.data.tokenType = fetchData.token_type;
resolve(fetchData);
}
} catch (err) {
console.log(err);
}
});
}
async isTokenValid() {
if (new Date() >= this.data.expiresIn) {
// Generate a new Token
try {
await this.getToken();
} catch (err) {
console.log(err);
}
}
// else - Do nothing and use current token saved in this.data.accessToken
}
async getData() {
try {
await this.isTokenValid();
await fetch("url2 with this.data.accessToken");
} catch (err) {
console.log(err);
}
}
}
let user = new StackClass("username", "password");
user.isTokenValid(); // SHOULD BE PRIVATE
user.getToken(); // SHOULD BE PRIVATE
user.getData(); // SHOULD BE PUBLIC
Define the isTokenValid and getToken as standalone functions instead. You should also avoid the explicit Promise construction antipattern, and only catch in a place where you can sensibly handle the error, which is usually in the caller of the asynchronous function (perhaps getData, perhaps in the consumer of the StackClass class):
class StackClass {
constructor(p1, p2) {
this.data = {
p1,
p2,
accessToken: null,
expiresIn: null,
tokenType: null
};
}
async getData() {
try {
await isTokenValid(this);
await fetch("url2 with this.data.accessToken");
} catch (err) {
// or you can just avoid the `try`/`catch` altogether,
// and have the consumer of getData handle problems
console.log(err);
}
}
}
function isTokenValid(stack) {
if (new Date() >= stack.data.expiresIn) {
// Generate a new Token
return getToken(stack);
}
// else - Do nothing and use current token saved in this.data.accessToken
}
async function getToken(stack) {
const fetchResponse = await fetch(
"url with this.data.p1 and this.data.p2"
);
const fetchData = await fetchResponse.json();
if (fetchData.status != 200) {
throw new Error('error');
} else {
// get the token and save it in this.data
stack.data.accessToken = fetchData.access_token;
stack.data.expiresIn = fetchData.expires_in;
stack.data.tokenType = fetchData.token_type;
}
}
Why don't you export a function that takes a list of arguments, creates an object of your class with those arguments, and then return a new object literal with only public methods? Take a look:
my-class.js
class MyClass {
constructor(f1, f2) {
this.field1 = f1;
this.field2 = f2;
}
m1() {
this.m2();
}
m2() {
console.log('this.field1', this.field1);
this.m3();
}
m3() {
console.log('this.field2', this.field2);
this._m4();
}
_m4() {
console.log('private m4');
this._m5();
}
_m5() {
console.log('private m5');
}
}
module.exports = (f1, f2) => {
const my = new MyClass(f1, f2);
return {
m1: my.m1.bind(my),
m2: my.m2.bind(my),
m3: my.m3.bind(my),
}
}
index.js
const myObj = require('./my-class')('f1', 'f2');
myObj.m1();
Live demo
This way you are hiding the private methods which should not be accessible outside of the module.
Related
I am fairly new to Node.JS, but have some experience in other languages. I am trying to achieve the following:
I want to perform a task and if it fails perform another task.
I have two files: one is the main function, the other contains the class.
First the main function (main.js):
(async function main() {
let { MyClass } = require("./my_class.js");
const mc = new MyClass();
await mc.do_stuff();
console.log(mc.message);
})();
The other is the class (my_class.js)
class MyClass {
constructor() {
this.message='hello';
}
do_stuff=async function() {
return new Promise((resolve,reject) => async function (){
let [res,rej]=await do_first('fail');
if(rej) {
console.log('do_first() failed.');
[res,rej]=await do_second('succeed');
if(rej) {
console.log('do_second() failed.');
reject('failed');
} else {
console.log('do_second() succeeded.');
resolve('success');
}
} else {
console.log('do_first() succeeded, no call to do_second().');
resolve('success');
}
});
}
do_first=async function(param) {
return new Promise((resolve,reject) => {
if(param==='fail') {
console.log('rejecting do_first()');
reject('failure');
} else {
console.log('resolving do_first()');
resole('success');
}
});
}
do_second=async function(param) {
return new Promise((resolve,reject) => {
if(param==='fail') {
console.log('rejecting do_second()');
reject('failure');
} else {
console.log('resolving do_second()');
resole('success');
}
});
}
}
exports.MyClass = MyClass
If I try to run it with node ./main.js nothing happens. If I run mc.do_stuff() without the await, I do get the hello... Which boats am I missing?
For that matter: I am running NodeJS v18.12.0
A few things need to be changed here to make it work:
When using await, only the Promise.resolve comes back to regular code execution. Promise.reject always raises an Exception.
Typo resole
changed code to consistently use arrow syntax. This requires referencing functions as instance members -> this.do_first. I suppose this was your intention. The syntax before did not execute the function as part of the object, but in global scope.
Here's your code in a working state
class MyClass {
constructor() {
this.message = "hello";
}
do_stuff = async () => {
return new Promise(async (resolve, reject) => {
try {
await this.do_first("fail");
console.log("do_first() succeeded, no call to do_second().");
resolve("success");
} catch (err) {
console.log("do_first() failed.");
try {
await this.do_second("succeed");
console.log("do_second() succeeded.");
resolve("success");
} catch (err) {
console.log("do_second() failed.");
reject("failed");
}
}
});
};
do_first = async (param) => {
return new Promise(async (resolve, reject) => {
if (param === "fail") {
console.log("rejecting do_first()");
reject("failure");
} else {
console.log("resolving do_first()");
resolve("success");
}
});
};
do_second = async (param) => {
return new Promise((resolve, reject) => {
if (param === "fail") {
console.log("rejecting do_second()");
reject("failure");
} else {
console.log("resolving do_second()");
resolve("success");
}
});
};
}
exports.MyClass = MyClass;
PS C:\Users\patrick\Documents\GitHub\stackoverflow-74714360> node ./main.js
rejecting do_first()
do_first() failed.
resolving do_second()
do_second() succeeded.
hello
Ima rookie using async/await but must now to use Redis-om. NN_walkd walks through a Redis database looking for loop-chains and does this by recursion. So the 2 questions I have is:
Am I calling the inner recursive NN_walkd calls correctly via async/await?
At runtime, the compSearchM proc is called first and seems to work (it gets 5 entries so it has to call NN_walkd 5 times). A NN_walkd is then recursively called, and then when it loops the 1st time it then calls compSearchK where the problems are. It seems to sit on the first Redis call in compSearchK (.search). Yet the code for compSearchK and compSearchM look basically identical.
main call
NN_walk = async function(req, db, cnode, pnode, chain, cb) {
var vegas, sneaker;
req.session.walk = [];
await NN_walkd(req, cnode, pnode, [], 1);
req.session.walk = null;
console.log('~~~~~~~~~~~~ Out of Walk ~~~~~~~~~~~~~~~');
cb();
};
redis.mjs
export class RedisDB {
constructor() {
...
this._companyRepo = ...
}
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
compSearchM(mkey) {
var tArr=[];
return new Promise(async (resolve) => {
const smkey = await this._companyRepo.search()
.where('MASTERKEY').equals(mkey)
.and('TBLNUM').equals(10)
.return.all();
if (smkey.length) {
for (var spot in smkey) {
const ttrr = await this._companyRepo.fetch(smkey[spot].entityId);
tArr.push(ttrr.toJSON());
}
resolve(tArr);
} else {
resolve(null);
}
});
}
walk.js
NN_walkd = async function(req, cnode, pnode, chain, lvl) {
...
if (cnode[1]) {
const sObj = await req.app.get('redis').compSearchK(cnode[1]);
if (sObj) {
int1 = (sObj.TBLNUM==1) ? null : sObj.CLIENTKEY;
(async () => await NN_walkd(req, [sObj.COMPANYKEY,int1], cnode, Array.from(chain), tlvl))()
}
} else {
const sArr = await req.app.get('redis').compSearchM(cnode[0]);
if (sArr.length) {
for (sneaker in sArr) {
(async () => await NN_walkd(req, [sArr[sneaker].COMPANYKEY,sArr[sneaker].CLIENTKEY], cnode, Array.from(chain), tlvl))()
}
} else {
console.log('no more links on this chain: ',cnode);
}
}
}
"doesn't matter if i have async or not here"
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
Of course it doesn't matter, because you're not using await inside of compSearchK!
You are using the explicit promise contructor anti-pattern. You should avoid it as it demonstrates lack of understanding. Here is compSearchK rewritten without the anti-pattern -
async compSearchK(ckey) {
// await key
const sckey =
await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
// return null if key is not found
if (sckey.length == 0) return null;
// otherwise get ttrr
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
// return ttrr as json
return ttrr.toJSON();
}
I used async-lock module for my typescript program of concurrency.
As I use this, I want to return the result in lock.acquire(...) {...}, but it's not working well.
How can I return the value? I'll be grateful for any advice about this. Thanks!
public async getValue(key: string): Promise<any> {
const base = this;
lock.acquire(key, async function (done) {
base.logger.info(`${key} lock enter`);
if (!await base.myRepository.checkDBTable(key)) {
const valueFromNetwork: number = await base.getValueFromNetwork(key);
const initResult: MyEntity = await base.myRepository.initNonce(key, valueFromNetwork);
if (!initResult) {
throw new Error('initValue failed...');
}
base.logger.debug(JSON.stringify(initResult, null, 4));
}
const valueFromDB: number = await base.myRepository.getValueFromDB(key);
if (valueFromDB === -1 || valueFromDB === undefined) {
throw new Error('getValueFromDB failed...');
} else {
const updateResult: MyEntity = await base.myRepository.updateValue(key, valueFromDB);
if (!updateResult) {
throw new Error('updateValue failed...');
}
base.logger.info(`${valueFromDB}`);
base.logger.info(`${key} lock done`);
done();
}
// I'd like to return valueFromDB above.
});
}
You have two options:
You can implement a wrapper around lock.aquire (Recommended as it is a little easier to read and can handle acquisition errors):
public async getValue(key: string): Promise<any> {
const base = this;
const done = await this.aquireLock(key);
base.logger.info(`${key} lock enter`);
if (!await base.myRepository.checkDBTable(key)) {
const valueFromNetwork: number = await base.getValueFromNetwork(key);
const initResult: MyEntity = await base.myRepository.initNonce(key, valueFromNetwork);
if (!initResult) {
throw new Error('initValue failed...');
}
base.logger.debug(JSON.stringify(initResult, null, 4));
}
const valueFromDB: number = await base.myRepository.getValueFromDB(key);
if (valueFromDB === -1 || valueFromDB === undefined) {
throw new Error('getValueFromDB failed...');
} else {
const updateResult: MyEntity = await base.myRepository.updateValue(key, valueFromDB);
if (!updateResult) {
throw new Error('updateValue failed...');
}
base.logger.info(`${valueFromDB}`);
base.logger.info(`${key} lock done`);
done();
}
return valueFromDB;
}
private async aquireLock(key: string): Promise<() => void> {
return new Promise((resolve, reject) => {
lock.acquire(key, done => {
resolve(done);
}, (err)=>{ // in case our aquire fails(times out, etc.)
if(err){
reject(err);
}
})
})
}
Playground
Or, you can use a function constructor (even if it is an anti-pattern):
getValue(key: string): Promise<any> {
return new Promise((resolve, reject) => {
const base = this;
lock.acquire(key, async function (done) {
base.logger.info(`${key} lock enter`);
if (!await base.myRepository.checkDBTable(key)) {
const valueFromNetwork: number = await base.getValueFromNetwork(key);
const initResult: MyEntity = await base.myRepository.initNonce(key, valueFromNetwork);
if (!initResult) {
reject(new Error('initValue failed...'));
}
base.logger.debug(JSON.stringify(initResult, null, 4));
}
const valueFromDB: number = await base.myRepository.getValueFromDB(key);
if (valueFromDB === -1 || valueFromDB === undefined) {
reject(new Error('getValueFromDB failed...'));
} else {
const updateResult: MyEntity = await base.myRepository.updateValue(key, valueFromDB);
if (!updateResult) {
reject(new Error('updateValue failed...'));
}
base.logger.info(`${valueFromDB}`);
base.logger.info(`${key} lock done`);
done();
}
resolve(valueFromDB)
});
})
}
Playground (Many things stubbed out, but it gets the general idea across)
I am trying to create a simple event emitter, and when I attempt to test the emitter it is showing up undefined through jest.
Code for the Event Emitter:
class EventEmitter {
constructor() {
this.events = {};
}
on(e, fn) {
if (this.events[e]) {
return this.event[e].add(fn);
}
return this.events[e] = new Set([fn]);
}
once(e, fn) {
if (this.events[e]) {
this.event[e].add(fn, 1);
}
this.events[e] = new Set([fn, 1]);
}
emit(e, ...args) {
if (this.events[e]) {
this.events[e].forEach(fn => {
if (typeof fn === 'function')
fn.apply(null, args);
});
if (this.events[e].has(1)) {
delete this.events[e];
}
} else if (!this.events[e]) {
console.log('Event Argument does not exist - please enter a valid event');
}
}
off(e) {
if (arguments.length === 0) {
this.events = {};
}
if (this.events[e]) {
delete this.events[e];
} else if (!this.events[e]) {
return console.log('Event Argument does not exist - please enter a valid event');
}
}
}
Code for the testing:
const EventEmitter = require('../index.js');
// Creating new Event Emitter
test('should Emit named events with a single argument', () => {
const ee = new EventEmitter();
ee.on('multiple', (x, y) => {
console.log(`First Argument: ${x}, Second Argument: ${y}`);
});
ee.on('single', () => "Why isnt this working")
const res = console.log(ee.emit('single'))
expect(res).toBe('Why isnt this working')
})
const res is returning undefined, but i am not sure why - any help would be so appreciated!
I am trying to load a hierarchy in my database. I have a column with parentId in my table so every row can have a parent. But I am having problems using recursion and promises.
function read (options) {
return serviceItemAttributeModel.findOne({
id: options.id,
id_organization: options.idOrganization
})
.then((attribute) => {
if (attribute) {
return loadChildren(attribute, attribute);
} else {
return attribute;
}
});
}
function loadChildren (root, attribute) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
if (!attributes) {
return root;
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each);
});
}
});
}
So, I call read that calls loadChildren to recursively try to load all entities (by looking children of an entity) and I get an undefined value. Any ideas?
I am also getting an error on console: a promise was created in a handler but was not returned from it.
EDIT:
Came up if this solution after Nosyara help. thanks!:
function read (options) {
return serviceItemAttributeModel.findOne({
where: {
id: options.attributeId,
id_organization: options.idOrganization
}
})
.then((attribute) => {
if (!attribute) {
return new Promise(function (resolve, reject) {
resolve(attribute);
});
} else {
return new Promise(function (resolve, reject) {
attribute.queryCount = 1;
resolve(attribute);
})
.then((attribute) => loadChildren(attribute, attribute));
}
});
}
function loadChildren (root, attribute) {
return new Promise(function (resolve, reject) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
attributes.length = attributes.length || 0;
root.queryCount = root.queryCount - 1 + attributes.length;
if (root.queryCount === 0) {
resolve(root);
} else if (root.queryCount > 10) {
let error = new Error('Service attribute hierarchy cant have more then 10 levels');
error.statusCode = 500;
reject(error);
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each).then(() => {
resolve(root);
});
});
}
});
});
}
You messing up with async calls and returns. You can convert both function to async, and pass through result structure to be updated. Example:
function read(...) {
return new Promise(function (accept, reject) {
// You code goes here, but instead of return
accept(resultFromAsyncFunction);
});
}
// ...
read(...).then(function(resultData) { ... });
Here is example of Promise recursion.