How to document a nested function within a method in JSdoc - javascript

In javascript I have a method which has a function inside of it, when trying to document It with the program JSdocs I assumed that It would have to look like this:
/**
* #memberof dataBase#query
* #inner
* #function makeArray
* #description ..
*/
or like this:
/**
* #function dataBase#query~makeArray
* #description ..
*/
But for some reason it does not show up.
Question: Is there any way for me to document this kind of function?
EDIT:
You may notice some places in my code below where on line continues on to the next line, this is simply because of the limited space provided by the stackoverflow question box and is not part of my source code.
Here is the code I want to document (specifically the makeArray function inside of the query method):
exports.dataBase = class dataBase {
constructor(connection, X64) {
this.connectString = connection
this.X64 = X64
this.connection = adodb.open(connection, X64)
this._this = this
this.shortcuts = require('./shortcuts')
}
async close() {
await this.connection.close()
return
}
async reopen() {
this.connection = adodb.open(this.connectString, this.X64)
return
}
async shortcut(type, data) {
await this.shortcuts.data[type](data, this._this)
return
}
async runSQL(sql) {
let type
if(sql.match('SELECT')) {
type = 'query'
} else {
type = 'execute'
}
let data
let self = this
await new Promise((resolve, reject) => {
self.connection[type](sql).then(result => {
data = result
resolve()
}).catch(err => {
console.log(err)
})
}).catch(err => {
console.log(err)
})
return data
}
async query(table, columns = '*' || [], rows = '*' || [], options = '*'
|| []) {
function makeArray(str) {
if(typeof str === 'string' && str !== '*') {
return [str]
}
}
makeArray(columns)
makeArray(rows)
makeArray(options)
function processData(table, columns, rows, options) {
function processColumns(columns) {
let retval = ''
for(let i in columns) {
if(i != columns.length - 1) {
retval += `${columns[i]},`
} else {
retval += `${columns[i]}`
return retval
}
}
}
function processRows(rows) {
let retval = ''
for(let i in rows) {
if(i != rows.length - 1) {
retval += `ID=${rows[i]} AND `
} else {
retval += `ID=${rows[i]}`
}
}
return retval
}
function processOptions(options) {
let retval = ''
for(let i in rows) {
retval += ` AND ${options[i]}`
}
return retval
}
let SQLcolumns = processColumns(columns)
let SQLrows = processRows(rows)
let SQLoptions = processOptions(options)
if(rows === '*' && options === '*') {
return `SELECT ${SQLcolumns} FROM [${table}];`
} else if(options === '*') {
return `SELECT ${SQLcolumns} FROM [${table}] WHERE ${SQLrows};`
} else if(rows === '*') {
return `SELECT ${SQLcolumns} FROM [${table}] WHERE ${SQLoptions};`
} else {
return `SELECT ${SQLcolumns} FROM [${table}] WHERE
${SQLrows}${SQLoptions};`
}
}
let data
await this.runSQL(processData(table, columns, rows,
options)).then((result) => {
data = result
}).catch(err => {
console.log(err)
})
return data
}
async createTable(name, columns, rows = null) {
function processColumns(columns) {
let retval = ''
for(let i of Object.keys(columns)) {
if(i !== Object.keys(columns)[columns.size() - 1]) {
retval += `${i} ${columns[i]},\n`
} else {
retval += `${i} ${columns[i]}`
}
}
return retval
}
let data
let SQLcolumns = processColumns(columns)
await this.runSQL(`CREATE TABLE ${name}
(\n${SQLcolumns}\n);`).then((result) => {
data = result
})
if(rows !== null) {
this.addRecords(name, rows)
}
}
async addRecords(table, values) {
let data = []
function processValues(values) {
let retval = ''
for(let i of Object.keys(values)) {
if(i !== Object.keys(values)[values.size() - 1]) {
retval += `${values[i]}, `
} else {
retval += values[i]
}
}
return retval
}
for(let i of values) {
let SQLvalues
SQLvalues = processValues(i)
await this.runSQL(`INSERT INTO [${table}] VALUES
(${SQLvalues});`).then((result) => {
data.push(result)
})
}
return data
}
}

Related

SQL Query Recursively Async Issue

My use case demands me to call an sql recursively till no rows are returned for which I have written the below code which due to async nature doesn't work as expected.
The piece of code which does this invocation is:
let Response = await getData(userId);
async function getData(userId) {
console.log("Invoking Get Data Function");
let arrayOfUserId = [userId];
let fetchMore = true,
j = 1;
let keyWithQoutes = -1;
return new Promise((resolve, reject) => {
do {
console.log(arrayOfUserId, j)
j++;
if (arrayOfUserId.length > 0) {
keyWithQoutes = arrayOfUserId.map((it) => {
return `'${it}'`;
});
}
const sql = ` Select userId from USER where reportingTo in (${arrayOfUserId})`;
console.log(' SQL Query ', sql);
con.query(sql, [], async(error, response) => {
if (error) {
fetchMore = false;
reject(error);
}
console.log(
" Response for ",
userId,
response,
response.length
);
if (response.length == 0) {
fetchMore = false;
resolve(arrayOfUserId);
}
else {
for (let i = 0; i < response.length; i++) {
console.log(response[i].userId);
arrayOfUserId.push(response[i].userId);
}
}
});
} while (fetchMore);
});
}

How do i fix the undefined issue that is logged in console for a articular date

async function getjsondata() {
try {
state = await fetch('https://covid.ourworldindata.org/data/owid-covid-data.json');
data = await state.json();
} catch (err) {
console.log(err);
}
}
var sumy = 0;
var tt_list = [];
async function getmonthnewcases() {
await getmonthsum();
await getjsondata();
await getCountry();
try {
for (let i = 0; i < 12; i++) {
for (let z = listy[i]; z <= listy[i + 1]; z++) {
for (key in data[isocount]["data"][z]) {
if (data[isocount]["data"][z].hasOwnProperty(key) == true) {
sumy += Number(data[isocount]["data"][z]["total_cases_per_million"]);
tt_list[i] = sumy;
} else if (typeof data[isocount]["data"][z]["total_cases_per_million"] === "undefined") {
data[isocount]["data"][z]["total_cases_per_million"] = sumy;
continue;
}
if (z == data[isocount]["data"].length - 1) {
break;
}
console.log(data[isocount]["data"][z]["date"], sumy, data[isocount]["data"][z]["total_cases_per_million"]);
}
}
}
console.log(tt_list);
} catch (err) {
console.log(err);
}
}
So this is how the code looks like am having trouble solving the issue of undefined in a particular month date from the api call, i have tried so many other options but nothing seems to be working please i nned some help
First is seems that you have some errors in the code,
You are not returning anything from getjsondata.
And you are not using the results in getmonthnewcases
(See code comments)
async function getjsondata() {
try {
state = await fetch('https://covid.ourworldindata.org/data/owid-covid-data.json');
return await state.json(); //<-- here
} catch (err) {
console.log(err);
}
}
var sumy = 0;
var tt_list = [];
async function getmonthnewcases() {
await getmonthsum();
const data = await getjsondata(); //<-- and here
await getCountry();
try {
for (let i = 0; i < 12; i++) {
for (let z = listy[i]; z <= listy[i + 1]; z++) {
for (key in data[isocount]["data"][z]) {
if (data[isocount]["data"][z].hasOwnProperty(key) == true) {
sumy += Number(data[isocount]["data"][z]["total_cases_per_million"]);
tt_list[i] = sumy;
} else if (typeof data[isocount]["data"][z]["total_cases_per_million"] === "undefined") {
data[isocount]["data"][z]["total_cases_per_million"] = sumy;
continue;
}
if (z == data[isocount]["data"].length - 1) {
break;
}
console.log(data[isocount]["data"][z]["date"], sumy, data[isocount]["data"][z]["total_cases_per_million"]);
}
}
}
console.log(tt_list);
} catch (err) {
console.log(err);
}
}

Function gets called before the subscribe call is completed in Angular

I am fetching datas from server but before the call gets completed the function gets called returning an empty array.I am new to RxJs could any one help me on it
getRows: (params) => {
setTimeout(() => {
const dataAfterSortingAndFiltering = this.sortAndFilter(audits.docs, params.sortModel, params.filterModel);
const rowsThisPage = dataAfterSortingAndFiltering.slice(0, audits.items.end);
let lastRow = -1;
if (dataAfterSortingAndFiltering.length <= params.endRow) {
lastRow = dataAfterSortingAndFiltering.length;
}
params.successCallback(rowsThisPage, lastRow);
}, 3000);
sortAndFilter function:
sortAndFilter(allOfTheData, sortModel, filterModel) {
return this.sortData(sortModel, this.filterData(filterModel, allOfTheData));
}
filterData function:
filterData(filterModel, data) {
const filterKeys = Object.keys(filterModel);
const filterPresent = filterModel && Object.keys(filterModel).length > 0;
if (!filterPresent) {
return data;
}
const resultOfFilter = [];
const filterParams = [];
for (let i = 0; i < filterKeys.length; i++) {
filterParams.push(`${filterKeys[i]}=${filterModel[filterKeys[i]].filter}`);
}
const params = filterParams.join('&');
this.auditService.getColumnSearch(params).pipe(first()).subscribe((datas: any) => {
resultOfFilter.push(...datas.docs);
});
return resultOfFilter;
}
SortData function:
sortData(sortModel, data) {
console.log('sortModel got called', sortModel);
console.log('data', data);
const sortPresent = sortModel && sortModel.length > 0;
if (!sortPresent) {
return data;
}
const resultOfSort = data.slice();
resultOfSort.sort((a, b) => {
for (let k = 0; k < sortModel.length; k++) {
const sortColModel = sortModel[k];
const valueA = a[sortColModel.colId];
const valueB = b[sortColModel.colId];
if (valueA == valueB) {
continue;
}
const sortDirection = sortColModel.sort === 'asc' ? 1 : -1;
if (valueA > valueB) {
return sortDirection;
} else {
return sortDirection * -1;
}
}
return 0;
});
return resultOfSort;
}
Before the server call gets completed the sortData function returns the data as [].
Leverage the feature of async and await
export class AuditComp {
getRows(params) {
setTimeout(() => {
const dataAfterSortingAndFiltering = this.sortAndFilter(audits.docs, params.sortModel, params.filterModel);
const rowsThisPage = dataAfterSortingAndFiltering.slice(0, audits.items.end);
let lastRow = -1;
if (dataAfterSortingAndFiltering.length <= params.endRow) {
lastRow = dataAfterSortingAndFiltering.length;
}
params.successCallback(rowsThisPage, lastRow);
}, 3000);
}
sortAndFilter(allOfTheData, sortModel, filterModel) {
return this.sortData(sortModel, this.filterData(filterModel, allOfTheData));
}
sortData(sortModel, data) {
console.log('sortModel got called', sortModel);
console.log('data', data);
const sortPresent = sortModel && sortModel.length > 0;
if (!sortPresent) {
return data;
}
const resultOfSort = data.slice();
resultOfSort.sort((a, b) => {
for (let k = 0; k < sortModel.length; k++) {
const sortColModel = sortModel[k];
const valueA = a[sortColModel.colId];
const valueB = b[sortColModel.colId];
if (valueA == valueB) {
continue;
}
const sortDirection = sortColModel.sort === 'asc' ? 1 : -1;
if (valueA > valueB) {
return sortDirection;
} else {
return sortDirection * -1;
}
}
return 0;
});
return resultOfSort;
}
async filterData(filterModel, data) {
const filterKeys = Object.keys(filterModel);
const filterPresent = filterModel && Object.keys(filterModel).length > 0;
if (!filterPresent) {
return data;
}
const resultOfFilter = [];
const filterParams = [];
for (let i = 0; i < filterKeys.length; i++) {
filterParams.push(`${filterKeys[i]}=${filterModel[filterKeys[i]].filter}`);
}
const params = filterParams.join('&');
await this.auditService.getColumnSearch(params).pipe(first()).toPromise()
.then((datas: any) => {
resultOfFilter.push(...datas.docs);
});
return resultOfFilter;
}
}
comment if faced any issue.

Asynchronous function is not working in order of process

I'm working with asynchronous javascript in NodeJS. I have a function, that modifies it's parameters and then resolve and emits to SocketIO client. The problem is, the function doesn't process the lines in order, it makes some process first and some process after it, I think it is just about asynchronous JavaScript problem, but I can't understand what to do for solve this.
My function,
const processStylesAndImages = (target, targetSlug, id, socket, styles, images, protocol, CSS, DOM) => {
return new Promise(async (resolve, reject) => {
let { coverage } = await CSS.takeCoverageDelta();
let { nodes } = await DOM.getFlattenedDocument({ depth: -1 });
styles = styles.filter(style => !style.isInline && style.sourceURL.trim().length > 0);
for(let style of styles) {
style.mediaQueries = [];
let m;
while(m = mediaQueryRegex.exec(style.css)) {
style.mediaQueries.push({
startOffset: m.index,
endOffset: m.index + m[0].length,
rule: style.css.slice(m.index, m.index + m[0].length),
used: true,
rules: []
});
}
style.used = [];
while(m = charsetRegex.exec(style.css)) {
style.used.push({
startOffset: m.index,
endOffset: m.index + m[0].length,
used: true,
styleSheetId: style.styleSheetId
});
}
while(m = importRegexVariable.exec(style.css)) {
style.used.push({
startOffset: m.index,
endOffset: m.index + m[0].length,
used: true,
styleSheetId: style.styleSheetId
});
}
let fontFaces = [];
while(m = fontFaceRegex.exec(style.css)) {
fontFaces.push(m);
}
fontFaces.forEach((m, index) => {
let pushed = false;
let props = css.parse(style.css.slice(m.index, m.index + m[0].length)).stylesheet.rules[0].declarations;
let fontFamily;
let fontWeight = null;
props.forEach(prop => {
if(prop.property == 'font-family') {
if(prop.value.startsWith("'") || prop.value.startsWith('"')) {
prop.value = prop.value.slice(1);
}
if(prop.value.endsWith("'") || prop.value.endsWith('"')) {
prop.value = prop.value.slice(0, -1);
}
prop.value = prop.value.toLowerCase();
fontFamily = prop.value;
} else if(prop.property == 'font-weight') {
fontWeight = prop.value;
}
});
if(fontWeight == null || 'normal') fontWeight = 400;
if(style.sourceURL == 'https://www.webmedya.com.tr/css/font-awesome.min.css') console.log(fontFamily, fontWeight);
nodes.forEach(async (node, nodeIndex) => {
let { computedStyle } = await CSS.getComputedStyleForNode({ nodeId: node.nodeId });
computedStyle = computedStyle.filter(item => {
return (item.name == 'font-family' || item.name == 'font-weight') && (item.value !== '' || typeof(item.value) !== 'undefined');
});
let elementFontFamily;
let elementFontWeight;
computedStyle.forEach(compute => {
if(compute.name == 'font-family' && compute.value !== '' && typeof(compute.value) !== 'undefined') {
elementFontFamily = compute.value.toLowerCase();
} else if(compute.name == 'font-weight') {
if(compute.value !== '' && typeof(compute.value) !== 'undefined') {
if(compute.value == 'normal') {
elementFontWeight = 400;
} else {
elementFontWeight = compute.value;
}
} else {
elementFontWeight = 400;
}
}
});
if(elementFontFamily && elementFontWeight) {
if(elementFontFamily.includes(fontFamily) && (elementFontWeight == fontWeight)) {
if(!pushed) {
//console.log(m);
style.used.push({
startOffset: m.index,
endOffset: m.index + m[0].length,
used: true,
styleSheetId: style.styleSheetId
});
pushed = true;
console.log('Pushed', style.css.slice(m.index, m.index + m[0].length));
}
}
}
});
});
console.log('BBBBBBBBBBBBB');
console.log('AAAAAAAAAAAA');
let parsedSourceURL = url.parse(style.sourceURL.trim());
if(parsedSourceURL.protocol === null && parsedSourceURL.host === null) {
if(style.sourceURL.trim().startsWith('/')) {
style.sourceURL = `${target}${style.sourceURL.trim()}`;
} else {
style.sourceURL = `${target}/${style.sourceURL.trim()}`;
}
};
style.parentCSS = "-1";
style.childCSSs = [];
style.childCSSs = getImports(style.css, style.sourceURL.trim(), target);
coverage.forEach(item => {
if(item.styleSheetId.trim() == style.styleSheetId.trim())
style.used.push(item);
});
style.mediaQueries.forEach((mediaQuery, index) => {
style.used.forEach((usedRule, usedIndex) => {
if(usedRule.startOffset > mediaQuery.startOffset && usedRule.endOffset < mediaQuery.endOffset) {
style.mediaQueries[index].rules.push(style.used[usedIndex]);
style.used[usedIndex] = false;
}
});
});
style.used = style.used.filter(item => {
return item !== false;
});
style.mediaQueries = style.mediaQueries.filter(item => {
return item.rules.length > 0;
});
style.mediaQueries.forEach((mediaQuery, index) => {
mediaQuery.rules.sort((a, b) => a.startOffset - b.startOffset);
});
style.used = style.used.concat(style.mediaQueries);
delete style.mediaQueries;
style.used.sort((a, b) => a.startOffset - b.startOffset);
let compressedCss = "";
if(style.used.length > 0) {
style.used.forEach(usedRule => {
if(usedRule.rule && usedRule.rules.length > 0) {
let queryRule = usedRule.rule.match(/#media[^{]+/)[0];
compressedCss += queryRule + '{';
usedRule.rules.forEach(item => {
compressedCss += style.css.slice(item.startOffset, item.endOffset);
});
compressedCss += '}';
} else {
compressedCss += style.css.slice(usedRule.startOffset, usedRule.endOffset);
}
});
};
style.compressedCss = compressedCss;
}
console.log('CCCCCCCCCCCCCCCCCCCC');
styles = preTraverse(styles, targetSlug, id);
debug('CSS Dosyaları İşlendi!');
fs.readFile(`./data/${targetSlug}/${id}/cimg/statistics.json`, async (err, data) => {
if(err) reject(err);
try {
data = JSON.parse(data);
await CSS.stopRuleUsageTracking();
await protocol.close();
if(typeof(styles) !== 'undefined' && styles.length > 0) {
debug('IMG Dosyaları İşlendi!');
socket.emit('log', { stage: 6, images, data, styles });
resolve({ images, data, styles });
} else {
debug('IMG Dosyaları İşlendi!');
socket.emit('log', { stage: 6, images, data, styles: [] });
resolve({ images, data, styles: [] });
};
} catch(e) {
reject(e);
};
});
});
};
Results when the functions starts for some parameters,
BBBBBBBBBBBBB
AAAAAAAAAAAA
fontawesome 400
BBBBBBBBBBBBB
AAAAAAAAAAAA
BBBBBBBBBBBBB
AAAAAAAAAAAA
CCCCCCCCCCCCCCCCCCCC
Pushed #font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}
Pushed #font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');unicode-range:U+0460-052F,U+20B4,U+2DE0-2DFF,U+A640-A69F}
Pushed #font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
The expected result is,
BBBBBBBBBBBBB
AAAAAAAAAAAA
fontawesome 400
Pushed #font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}
BBBBBBBBBBBBB
AAAAAAAAAAAA
Pushed #font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');unicode-range:U+0460-052F,U+20B4,U+2DE0-2DFF,U+A640-A69F}
Pushed #font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
BBBBBBBBBBBBB
AAAAAAAAAAAA
CCCCCCCCCCCCCCCCCCCC
The function is skips the for loop at line 6 in JSFiddle. It behaves like asynchronous process, but I want to it behave like synchronous.
Thanks!
You should await the new Promise((res, rej) => { promise on line 39 in your fiddle. You create a promise with .then() and .catch() handlers which you run it within your loop, but don't await it. Meaning, the promise is triggered, but the code continues to the next iteration already.
So try to add await in front of that new Promise(...) on line 39 and run it.

How to query almost the entire postgresql database for a property on every item? (Using sequelize and Node.js)

I'm trying to scan through an entire tree in my database, looking for two properties ('title' and 'id') for every item, and then I need to check if there's a linked database table below the current table, and if so, I need to perform the same actions on that table.
Once I get the whole tree, I need to insert those into a master variable that I can then import elsewhere throughout my web app.
(I'm at the point where I'm pretty sure I'm reinventing the wheel. I've searched the Sequelize documentation, but if there's a simple solution, I didn't see it.)
Here's the relevant portions of my current code (it's not fully working yet, but should give the idea).
(You can find the full project repository by clicking this link here.)
```
const buildTopicTreeFromCurrentDatabase = (callback) => {
let topicTree = [];
let isFinished = false;
const isSearchFinished = new Emitter();
console.log(`emitter created`);
isSearchFinished.on('finished', () => {
console.log(`the search is done`);
if (isFinished = true) {
callback(null, this.primaryTopicsShort)
};
});
/* need to go back and refactor -- violates DRY */
this.primaryTopicsShort = [];
PrimaryTopic.all()
.then((primaryTopics) => {
if (primaryTopics.length === 0) {
return callback('no Primary Topics defined');
}
for (let i = 0; i < primaryTopics.length; i++) {
this.primaryTopicsShort[i] = {
title: primaryTopics[i].title,
id: primaryTopics[i].id,
secondaryTopics: []
};
PrimaryTopic.findById(this.primaryTopicsShort[i].id, {
include: [{
model: SecondaryTopic,
as: 'secondaryTopics'
}]
})
.then((currentPrimaryTopic) => {
if (currentPrimaryTopic.secondaryTopics.length !== 0) {
for (let j = 0; j < currentPrimaryTopic.secondaryTopics.length; j++) {
this.primaryTopicsShort[i].secondaryTopics[j] = {
title: currentPrimaryTopic.secondaryTopics[j].title,
id: currentPrimaryTopic.secondaryTopics[j].id,
thirdTopics: []
};
SecondaryTopic.findById(this.primaryTopicsShort[i].secondaryTopics[j].id, {
include: [{
model: ThirdTopic,
as: 'thirdTopics'
}]
})
.then((currentSecondaryTopic) => {
if (currentPrimaryTopic.secondaryTopics.length - 1 === j) {
isSearchFinished.emit('finished');
}
if (currentSecondaryTopic.thirdTopics.length !== 0) {
for (let k = 0; k < currentSecondaryTopic.thirdTopics.length; k++) {
this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k] = {
title: currentSecondaryTopic.thirdTopics[k].title,
id: currentSecondaryTopic.thirdTopics[k].id,
fourthTopics: []
};
ThirdTopic.findById(this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].id, {
include: [{
model: FourthTopic,
as: 'fourthTopics'
}]
})
.then((currentThirdTopics) => {
if (currentThirdTopics.fourthTopics.length !== 0) {
for (let l = 0; l < currentThirdTopics.fourthTopics.length; l++) {
this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].fourthTopics[k] = {
title: currentThirdTopics.fourthTopics[l].title,
id: currentThirdTopics.fourthTopics[l].id,
fifthTopics: []
}
FourthTopic.findById(this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].fourthTopics[l].id, {
include: [{
model: FifthTopic,
as: 'fifthTopics'
}]
})
.then((currentFourthTopics) => {
if (currentFourthTopics.fifthTopics.length !== 0) {
for (let m = 0; m < currentFourthTopics.fifthTopics.length; m++) {
this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].fourthTopics[k].fifthTopics[m] = {
title: currentFourthTopics.fifthTopics[m].title,
id: currentFourthTopics.fifthTopics[m].id
}
}
}
})
.catch((err) => {
callback(err);
});
}
}
})
.catch((err) => {
callback(err)
});
}
}
})
.catch((err) => {
callback(err);
})
}
}
})
.catch((err) => {
callback(err);
})
}
})
.catch((err) => {
callback(err);
});
};
There are a few problems with this code.
First, I need to be using a DRY and recursive solution, since the database structure may change in the future.
Second, I've played around a lot with the 'finished' emitter, but I haven't figured out yet how to place it so that the event is emitted at the end of searching the database, and also so that I don't cycle back through the database multiple times.
I've been working on the following recursive solution, but the hours keep stretching by and I don't feel like I'm getting anywhere.
const buildDatabaseNames = (DatabaseNameStr, callback) => {
let camelSingular = DatabaseNameStr.slice(0,1);
camelSingular = camelSingular.toLowerCase();
camelSingular = camelSingular + DatabaseNameStr.slice(1, DatabaseNameStr.length);
let camelPlural = DatabaseNameStr.slice(0,1);
camelPlural = camelPlural.toLowerCase();
camelPlural = camelPlural + DatabaseNameStr.slice(1, DatabaseNameStr.length) + 's';
let databaseNameStr = `{ "capsSingular": ${"\"" + DatabaseNameStr + "\""}, "capsPlural": ${"\"" + DatabaseNameStr + 's' + "\""}, "camelSingular": ${"\"" + camelSingular + "\""}, "camelPlural": ${"\"" + camelPlural + "\""} }`;
let names = JSON.parse(databaseNameStr);
return callback(names);
};
const isAnotherDatabase = (DatabaseName, id, NextDatabaseName) => {
DatabaseName.findById({
where: {
id: id
}
})
.then((res) => {
if (typeof res.NextDatabaseName === undefined) {
return false;
} else if (res.NextDatabaseName.length === 0) {
return false;
} else {
return true;
}
})
.catch((err) => {
console.error(err);
process.exit();
});
};
const searchDatabase = (first, i, current, next, list, callback) => {
if (typeof first === 'string') {
first = buildDatabaseNames(first);
current = buildDatabaseNames(current);
next = buildDatabaseNames(next);
}
if (first === current) {
this.first = current;
let topicTree = [];
const isSearchFinished = new Emitter();
console.log(`emitter created`);
isSearchFinished.on('finished', () => {
console.log(`the search is done`);
callback(null, this.primaryTopicsShort);
});
}
current.CapsSingular.all()
.then((res) => {
current.camelPlural = res;
if (if current.camelPlural.length !== 0) {
for (let j = 0; j < currentParsed.camelPlural.length; j++) {
if (first === current) {
this.first[i].current[j].title = current.camelPlural[j].title,
this.first[i].current[j].id = current.camelPlural[j].id
next.camelSingular = []
} else {
this.first[i]..current[j].title = current.camelPlural[j].title,
this.first[i].id = current.camelPlural[j].id,
next.camelSingular = []
}
let isNext = isAnotherDatabase(current.)
searchDatabase(null, j, next, list[i + 1], list, callback).bind(this);
}
} else {
callback(null, this.first);
}
})
.catch((err) => {
callback(err);
});
};
I decided to stop and ask for help, as I just realized that in order to make the properties (this.first[i].current[j].title = current.camelPlural[j].title) on each recursive iteration accurate, I'll have to do a JSON.stringify, alter the string for the next iteration, place all the required iterations of itinto a variable, pass it into the next recursion, and then do JSON.parse again afterwards. It seems like I'm making this ridiculously complicated?
Any help is appreciated, thank you.
While I wasn't able to make a recursive, generic solution, I did at least find some result.
const buildTopicTreeFromCurrentDatabase = (callback) => {
let topicTree = [];
const isSearchFinished = new Emitter();
isSearchFinished.on('finished', () => {
if (isFinished === 0) {
callback(null, this.primaryTopicsShort);
};
});
/* need to go back and refactor -- violates DRY */
this.primaryTopicsShort = [];
let isFinished = 0;
isFinished = isFinished++;
PrimaryTopic.all()
.then((primaryTopics) => {
isFinished = isFinished + primaryTopics.length;
if (primaryTopics.length === 0) {
return callback('no Primary Topics defined');
}
for (let i = 0; i < primaryTopics.length; i++) {
if (i === 0) {
var isLastPrimary = false;
};
this.primaryTopicsShort[i] = {
title: primaryTopics[i].title,
id: primaryTopics[i].id,
secondaryTopics: []
};
PrimaryTopic.findById(this.primaryTopicsShort[i].id, {
include: [{
model: SecondaryTopic,
as: 'secondaryTopics'
}]
})
.then((currentPrimaryTopic) => {
isFinished = isFinished - 1 + currentPrimaryTopic.secondaryTopics.length;
if (i === primaryTopics.length - 1) {
isLastPrimary = true;
};
if (currentPrimaryTopic.secondaryTopics.length === 0 && isLastPrimary) {
isSearchFinished.emit('finished');
} else if (currentPrimaryTopic.secondaryTopics.length !== 0) {
for (let j = 0; j < currentPrimaryTopic.secondaryTopics.length; j++) {
if (j === 0) {
var isLastSecondary = false;
}
this.primaryTopicsShort[i].secondaryTopics[j] = {
title: currentPrimaryTopic.secondaryTopics[j].title,
id: currentPrimaryTopic.secondaryTopics[j].id,
thirdTopics: []
};
SecondaryTopic.findById(this.primaryTopicsShort[i].secondaryTopics[j].id, {
include: [{
model: ThirdTopic,
as: 'thirdTopics'
}]
})
.then((currentSecondaryTopic) => {
isFinished = isFinished - 1 + currentSecondaryTopic.thirdTopics.length;
if (j === currentPrimaryTopic.secondaryTopics.length - 1) {
isLastSecondary = true;
}
if (currentSecondaryTopic.thirdTopics.length === 0 && isLastPrimary && isLastSecondary) {
isSearchFinished.emit('finished');
} else if (currentSecondaryTopic.thirdTopics.length !== 0) {
for (let k = 0; k < currentSecondaryTopic.thirdTopics.length; k++) {
if (k === 0) {
var isLastThird = false;
};
this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k] = {
title: currentSecondaryTopic.thirdTopics[k].title,
id: currentSecondaryTopic.thirdTopics[k].id,
fourthTopics: []
};
ThirdTopic.findById(this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].id, {
include: [{
model: FourthTopic,
as: 'fourthTopics'
}]
})
.then((currentThirdTopic) => {
isFinished = isFinished - 1 + currentThirdTopic.fourthTopics.length;
if (k === currentSecondaryTopic.thirdTopics.length - 1) {
isLastThird = true;
};
if (currentThirdTopic.fourthTopics.length === 0 && isLastPrimary && isLastSecondary && isLastThird) {
isSearchFinished.emit('finished');
} else if (currentThirdTopic.fourthTopics.length !== 0) {
for (let l = 0; l < currentThirdTopic.fourthTopics.length; l++) {
if (l = 0) {
var isLastFourth = false;
}
this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].fourthTopics[k] = {
title: currentThirdTopic.fourthTopics[l].title,
id: currentThirdTopic.fourthTopics[l].id,
fifthTopics: []
}
FourthTopic.findById(this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].fourthTopics[l].id, {
include: [{
model: FifthTopic,
as: 'fifthTopics'
}]
})
.then((currentFourthTopics) => {
isFinished = isFinished - 1 + currentFourthTopics.fifthTopics.length;
if (l = currentThirdTopic.fourthTopics.length - 1) {
isLastFourth = true;
}
if (currentFourthTopic.fifthTopics.length === 0 && isLastPrimary && isLastSecondary && isLastThird && isLastFourth) {
isSearchFinished.emit('finished');
} else if (currentFourthTopic.fifthTopics.length !== 0) {
for (let m = 0; m < currentFourthTopic.fifthTopics.length; m++) {
if (m = 0) {
var isLastFifth = false;
}
if (m === currentFourthTopic.fifthTopics.length - 1) {
isLastFifth = true;
}
this.primaryTopicsShort[i].secondaryTopics[j].thirdTopics[k].fourthTopics[k].fifthTopics[m] = {
title: currentFourthTopic.fifthTopics[m].title,
id: currentFourthTopic.fifthTopics[m].id
};
if (isLastPrimary === true && isLastSecondary === true && isLastThird === true && isLastFourth === true && isLastFifth === true) {
isFinished = isFinished - 1;
isSearchFinished.emit('finished');
};
}
}
})
.catch((err) => {
callback(err);
});
}
}
})
.catch((err) => {
callback(err)
});
}
}
})
.catch((err) => {
callback(err);
})
}
}
})
.catch((err) => {
callback(err);
});
}
})
.catch((err) => {
callback(err);
});
};
I eventually learned that the root of the problem I face was in the asynchronous nature of the database calls.
To prevent the final callback from getting fired before all the calls were complete, I used something called a semaphore. It's a thing used often in other languages, but not often used in JavaScript (so I hear).
You can see how it works by looking at the variable isFinished. The value starts at zero and goes up for every entry it retrieves from the database. When it finishes processing the data, the code subtracts 1 from the total value. The final callback event (in the emitter), can only go off once the isFinished value returns to zero.
This isn't the most elegant solution. As #Timshel said, it probably would have been wiser not to design this database portion not have so many different tables.
But this solution will do for now.
Thanks.

Categories