Asynchronous function is not working in order of process - javascript

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.

Related

Javascript RangeError on website proxy RangeError: Failed to construct 'Response': The status provided (0) is outside the range [200, 599]

Image Here
Hello! I am trying to add a search bar that acts like a proxy at the top of my website. Whenever I input a website like https://stackoverflow.com I receive an error on the website. RangeError: Failed to construct 'Response': The status provided (0) is outside the range [200, 599] (Note: I am using the ultraviolet service) If you'd like to test yourself the website is https://lun.netlify.app also no errors seem to appear in the console log.
importScripts('/uv/uv.bundle.js');
importScripts('/uv/uv.config.js');
class UVServiceWorker extends EventEmitter {
constructor(config = __uv$config) {
super();
if (!config.bare) config.bare = '/bare/';
this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location));
this.headers = {
csp: [
'cross-origin-embedder-policy',
'cross-origin-opener-policy',
'cross-origin-resource-policy',
'content-security-policy',
'content-security-policy-report-only',
'expect-ct',
'feature-policy',
'origin-isolation',
'strict-transport-security',
'upgrade-insecure-requests',
'x-content-type-options',
'x-download-options',
'x-frame-options',
'x-permitted-cross-domain-policies',
'x-powered-by',
'x-xss-protection',
],
forward: [
'accept-encoding',
'connection',
'content-length',
'content-type',
'user-agent',
],
};
this.method = {
empty: [
'GET',
'HEAD'
]
};
this.statusCode = {
empty: [
204,
304,
],
};
this.config = config;
};
async fetch({ request }) {
if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) {
return fetch(request);
};
try {
const ultraviolet = new Ultraviolet(this.config);
if (typeof this.config.construct === 'function') {
this.config.construct(ultraviolet, 'service');
};
const db = await ultraviolet.cookie.db();
ultraviolet.meta.origin = location.origin;
ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url));
const requestCtx = new RequestContext(
request,
this,
ultraviolet,
!this.method.empty.includes(request.method.toUpperCase()) ? await request.blob() : null
);
if (ultraviolet.meta.url.protocol === 'blob:') {
requestCtx.blob = true;
requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname);
};
if (request.referrer && request.referrer.startsWith(location.origin)) {
const referer = new URL(ultraviolet.sourceUrl(request.referrer));
if (ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') {
requestCtx.headers.origin = referer.origin;
};
requestCtx.headers.referer = referer.href;
};
const cookies = await ultraviolet.cookie.getCookies(db) || [];
const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false);
const browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName();
if (browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) {
requestCtx.forward.shift();
};
if (cookieStr) requestCtx.headers.cookie = cookieStr;
const reqEvent = new HookEvent(requestCtx, null, null);
this.emit('request', reqEvent);
if (reqEvent.intercepted) return reqEvent.returnValue;
const response = await fetch(requestCtx.send);
if (response.status === 500) {
return Promise.reject('');
};
const responseCtx = new ResponseContext(requestCtx, response, this);
const resEvent = new HookEvent(responseCtx, null, null);
this.emit('beforemod', resEvent);
if (resEvent.intercepted) return resEvent.returnValue;
for (const name of this.headers.csp) {
if (responseCtx.headers[name]) delete responseCtx.headers[name];
};
if (responseCtx.headers.location) {
responseCtx.headers.location = ultraviolet.rewriteUrl(responseCtx.headers.location);
};
if (responseCtx.headers['set-cookie']) {
Promise.resolve(ultraviolet.cookie.setCookies(responseCtx.headers['set-cookie'], db, ultraviolet.meta)).then(() => {
self.clients.matchAll().then(function (clients){
clients.forEach(function(client){
client.postMessage({
msg: 'updateCookies',
url: ultraviolet.meta.url.href,
});
});
});
});
delete responseCtx.headers['set-cookie'];
};
if (responseCtx.body) {
switch(request.destination) {
case 'script':
case 'worker':
responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`;
responseCtx.body += ultraviolet.js.rewrite(
await response.text()
);
break;
case 'style':
responseCtx.body = ultraviolet.rewriteCSS(
await response.text()
);
break;
case 'iframe':
case 'document':
if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) {
responseCtx.body = ultraviolet.rewriteHtml(
await response.text(),
{
document: true ,
injectHead: ultraviolet.createHtmlInject(
this.config.handler,
this.config.bundle,
this.config.config,
ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true),
request.referrer
)
}
);
};
};
};
if (requestCtx.headers.accept === 'text/event-stream') {
responseCtx.headers['content-type'] = 'text/event-stream';
};
this.emit('response', resEvent);
if (resEvent.intercepted) return resEvent.returnValue;
return new Response(responseCtx.body, {
headers: responseCtx.headers,
status: responseCtx.status,
statusText: responseCtx.statusText,
});
} catch(err) {
return new Response(err.toString(), {
status: 500,
});
};
};
getBarerResponse(response) {
const headers = {};
const raw = JSON.parse(response.headers.get('x-bare-headers'));
for (const key in raw) {
headers[key.toLowerCase()] = raw[key];
};
return {
headers,
status: +response.headers.get('x-bare-status'),
statusText: response.headers.get('x-bare-status-text'),
body: !this.statusCode.empty.includes(+response.headers.get('x-bare-status')) ? response.body : null,
};
};
get address() {
return this.addresses[Math.floor(Math.random() * this.addresses.length)];
};
static Ultraviolet = Ultraviolet;
};
self.UVServiceWorker = UVServiceWorker;
class ResponseContext {
constructor(request, response, worker) {
const { headers, status, statusText, body } = !request.blob ? worker.getBarerResponse(response) : {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries([...response.headers.entries()]),
body: response.body,
};
this.request = request;
this.raw = response;
this.ultraviolet = request.ultraviolet;
this.headers = headers;
this.status = status;
this.statusText = statusText;
this.body = body;
};
get url() {
return this.request.url;
}
get base() {
return this.request.base;
};
set base(val) {
this.request.base = val;
};
};
class RequestContext {
constructor(request, worker, ultraviolet, body = null) {
this.ultraviolet = ultraviolet;
this.request = request;
this.headers = Object.fromEntries([...request.headers.entries()]);
this.method = request.method;
this.forward = [...worker.headers.forward];
this.address = worker.address;
this.body = body || null;
this.redirect = request.redirect;
this.credentials = 'omit';
this.mode = request.mode === 'cors' ? request.mode : 'same-origin';
this.blob = false;
};
get send() {
return new Request((!this.blob ? this.address.href + 'v1/' : 'blob:' + location.origin + this.url.pathname), {
method: this.method,
headers: {
'x-bare-protocol': this.url.protocol,
'x-bare-host': this.url.hostname,
'x-bare-path': this.url.pathname + this.url.search,
'x-bare-port': this.url.port || (this.url.protocol === 'https:' ? '443' : '80'),
'x-bare-headers': JSON.stringify(this.headers),
'x-bare-forward-headers': JSON.stringify(this.forward),
},
redirect: this.redirect,
credentials: this.credentials,
mode: location.origin !== this.address.origin ? 'cors' : this.mode,
body: this.body
});
};
get url() {
return this.ultraviolet.meta.url;
};
set url(val) {
this.ultraviolet.meta.url = val;
};
get base() {
return this.ultraviolet.meta.base;
};
set base(val) {
this.ultraviolet.meta.base = val;
};
}
function isHtml(url, contentType = '') {
return (Ultraviolet.mime.contentType((contentType || url.pathname)) || 'text/html').split(';')[0] === 'text/html';
};
class HookEvent {
#intercepted;
#returnValue;
constructor(data = {}, target = null, that = null) {
this.#intercepted = false;
this.#returnValue = null;
this.data = data;
this.target = target;
this.that = that;
};
get intercepted() {
return this.#intercepted;
};
get returnValue() {
return this.#returnValue;
};
respondWith(input) {
this.#returnValue = input;
this.#intercepted = true;
};
};
var R = typeof Reflect === 'object' ? Reflect : null
var ReflectApply = R && typeof R.apply === 'function'
? R.apply
: function ReflectApply(target, receiver, args) {
return Function.prototype.apply.call(target, receiver, args);
}
var ReflectOwnKeys
if (R && typeof R.ownKeys === 'function') {
ReflectOwnKeys = R.ownKeys
} else if (Object.getOwnPropertySymbols) {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target));
};
} else {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target);
};
}
function ProcessEmitWarning(warning) {
if (console && console.warn) console.warn(warning);
}
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
return value !== value;
}
function EventEmitter() {
EventEmitter.init.call(this);
}
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;
var defaultMaxListeners = 10;
function checkListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
}
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
}
defaultMaxListeners = arg;
}
});
EventEmitter.init = function() {
if (this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
}
this._maxListeners = n;
return this;
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this);
};
EventEmitter.prototype.emit = function emit(type) {
var args = [];
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
var doError = (type === 'error');
var events = this._events;
if (events !== undefined)
doError = (doError && events.error === undefined);
else if (!doError)
return false;
if (doError) {
var er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
throw er;
}
var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
err.context = er;
throw err;
}
var handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
ReflectApply(handler, this, args);
} else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
ReflectApply(listeners[i], this, args);
}
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
checkListener(listener);
events = target._events;
if (events === undefined) {
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
events = target._events;
}
existing = events[type];
}
if (existing === undefined) {
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + String(type) + ' listeners ' +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
ProcessEmitWarning(w);
}
}
return target;
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
var wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;
checkListener(listener);
events = this._events;
if (events === undefined)
return this;
list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
originalListener = list[i].listener;
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== undefined)
this.emit('removeListener', type, originalListener || listener);
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events, i;
events = this._events;
if (events === undefined)
return this;
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else
delete events[type];
}
return this;
}
if (arguments.length === 0) {
var keys = Object.keys(events);
var key;
for (i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
function _listeners(target, type, unwrap) {
var events = target._events;
if (events === undefined)
return [];
var evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
var events = this._events;
if (events !== undefined) {
var evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
copy[i] = arr[i];
return copy;
}
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
function unwrapListeners(arr) {
var ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
ret[i] = arr[i].listener || arr[i];
}
return ret;
}
function once(emitter, name) {
return new Promise(function (resolve, reject) {
function errorListener(err) {
emitter.removeListener(name, resolver);
reject(err);
}
function resolver() {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
if (name !== 'error') {
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
}
});
}
function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
if (typeof emitter.on === 'function') {
eventTargetAgnosticAddListener(emitter, 'error', handler, flags);
}
}
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.on === 'function') {
if (flags.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else if (typeof emitter.addEventListener === 'function') {
emitter.addEventListener(name, function wrapListener(arg) {
if (flags.once) {
emitter.removeEventListener(name, wrapListener);
}
listener(arg);
});
} else {
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
}
}

Send multiple requests separate instead of array in Javascript

This is part of code (code not mine, I can't understand how it's working, maybe it's called Promise, but I'm not sure).
m = {
mounted: function() {
var e = this;
this.$bus.on("buff-event", (function(t) {
e.buff(t)
}))
},
methods: {
buff: function(e) {
var t = this;
this.$bus.emit("bfceebecbb-change", "Motivating...");
var n = '[{"__class__":"ServerRequest","requestData":[],"requestClass":"OtherPlayerService","requestMethod":"' + e + '","requestId":%%requestId%%}]';
this.requestFaker.fetch(n).then((function(i) {
i = (i = i.filter((function(t) {
return t.requestMethod == e
}))[0]).responseData.filter((function(e) {
return void 0 === e.next_interaction_in && !e.is_self && (void 0 === e.accepted || 1 == e.accepted)
})), n = [], _.forEach(i, (function(e) {
n.push('{"__class__":"ServerRequest","requestData":[' + e.player_id + '],"requestClass":"OtherPlayerService","requestMethod":"polivateRandomBuilding","requestId":%%requestId%%}')
})), n.length ? (n = "[" + n.join(",") + "]", t.requestFaker.fetch(n).then((function() {
t.$bus.emit("bfceebecbb-change", "Motivation success")
}))) : t.$bus.emit("bfceebecbb-change", "Nobody to motivate")
}))
}
}
},
This code collecting data of users, store in to array and then pass it to the server. So it sending big array of all users in one request (it pushing all entries to array in this part n.push('{"__class__":"ServerRequest"...).
What I want to achieve is to send requests for each user one by one with some delays (for example 1 second delay between requests).
I've tried many ways to achieve it, but all unsuccessfully, I'm lack of knowledge about this programming language.
I've tried to use setTimeout functions in different ways, but all the time unsuccessfully:
methods: {
buff: function(e) {
var t = this;
this.$bus.emit("bfceebecbb-change", "Motivating...");
var n = '[{"__class__":"ServerRequest","requestData":[],"requestClass":"OtherPlayerService","requestMethod":"' + e + '","requestId":%%requestId%%}]';
this.requestFaker.fetch(n).then((function(i) {
i = (i = i.filter((function(t) {
return t.requestMethod == e
}))[0]).responseData.filter((function(e) {
return void 0 === e.next_interaction_in && !e.is_self && (void 0 === e.accepted || 1 == e.accepted)
})), n = [], _.forEach(i, (function(e) {
n.push('{"__class__":"ServerRequest","requestData":[' + e.player_id + '],"requestClass":"OtherPlayerService","requestMethod":"polivateRandomBuilding","requestId":%%requestId%%}')
})), n.length ?
setTimeout((function() {
(setTimeout((function() {n = "[" + n.join(",") + "]"}), 1000) , t.requestFaker.fetch(n).then((function() {
t.$bus.emit("bfceebecbb-change", "Motivation success")
})))}), 1000) : t.$bus.emit("bfceebecbb-change", "Nobody to motivate")
}))
}
}
Also tried like this:
methods: {
buff: function(e) {
var t = this;
this.$bus.emit("bfceebecbb-change", "Motivating...");
var n = '[{"__class__":"ServerRequest","requestData":[],"requestClass":"OtherPlayerService","requestMethod":"' + e + '","requestId":%%requestId%%}]';
this.requestFaker.fetch(n).then((function(i) {
i = (i = i.filter((function(t) {
return t.requestMethod == e
}))[0]).responseData.filter((function(e) {
return void 0 === e.next_interaction_in && !e.is_self && (void 0 === e.accepted || 1 == e.accepted)
})), n = [], _.forEach(i, (function(e) {
n.push('{"__class__":"ServerRequest","requestData":[' + e.player_id + '],"requestClass":"OtherPlayerService","requestMethod":"polivateRandomBuilding","requestId":%%requestId%%}')
})), n.length ?
setTimeout((function() {
(n = "[" + n.join(",") + "]", t.requestFaker.fetch(n).then((function() {
t.$bus.emit("bfceebecbb-change", "Motivation success")
})))}), 1000) : t.$bus.emit("bfceebecbb-change", "Nobody to motivate")
}))
}
}
UPDATED
As per comment asked what is %%requestId%% - I found this part:
{
key: "fetch",
value: function(e) {
function t(t) {
return e.apply(this, arguments)
}
return t.toString = function() {
return e.toString()
}, t
}((function(e) {
for (var t = e;
(t = e.replace("%%requestId%%", this.requestId)) !== e;) e = t, this.incRequestId();
return fetch(gameVars.gatewayUrl, this.getHeadForFetchQuery(e)).then((function(e) {
return e.json()
}))
}))
}
try this
in general, forEach should be replaced with for..of loop with using async/await helper function which will delay the request (in await delaySome(1000); part)
it should send data in the same format, and if it fails at the server, that's probably where it needs tweaking.. or everywhere else..
m = {
mounted: function() {
var e = this;
this.$bus.on("buff-event", function(t) {
e.buff(t);
});
},
methods: {
buff: function(e) {
var t = this;
function delaySome(delay) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, delay);
});
}
this.$bus.emit("bfceebecbb-change", "Motivating...");
var n = '[{"__class__":"ServerRequest","requestData":[],"requestClass":"OtherPlayerService","requestMethod":"' + e + '","requestId":%%requestId%%}]';
this.requestFaker.fetch(n).then(async function(i) {
(i = (i = i.filter(function(t) {
return t.requestMethod == e;
})[0]).responseData.filter(function(e) {
return void 0 === e.next_interaction_in && !e.is_self && (void 0 === e.accepted || 1 == e.accepted);
})),
(n = []);
if (i.length) {
for (const e of i) {
await delaySome(1000);
t.requestFaker.fetch('[{"__class__":"ServerRequest","requestData":[' + e.player_id + '],"requestClass":"OtherPlayerService","requestMethod":"polivateRandomBuilding","requestId":%%requestId%%}]').then(function() {
t.$bus.emit("bfceebecbb-change", "Motivation success");
})
}
} else {
t.$bus.emit("bfceebecbb-change", "Nobody to motivate")
}
});
},
},
};

How to document a nested function within a method in JSdoc

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

How to make a function that is wrapped inside a function returns on behalf of the parent function

I have a function that inside a function. I want the parent function to return an updated object (after the loop ends), currently, it returns undefined because the parent function returns nothing, only the child function function(items) returns data. How can I make the parent function returns updated return_data? Thanks a lot.
reportParser: (report) => {
const return_data = {
"click": "[id^='AUTOGENBOOKMARK_75_'].style_12",
"clicker": "[id^='AUTOGENBOOKMARK_74_'].style_12",
"openning": ".style_25 > .style_24",
"openner": ".style_25 > .style_24",
"name": "[id^='AUTOGENBOOKMARK_7_'].style_12",
"subject": "[id^='AUTOGENBOOKMARK_9_'].style_12",
"audience": "[id^='AUTOGENBOOKMARK_11_'].style_12",
"sent_mails": "[id^='AUTOGENBOOKMARK_20_'].style_12",
"send_date": "[id^='AUTOGENBOOKMARK_32_'].style_12",
"cancel_subscription_click": ".style_25 > .style_24",
"cancel_subscription_clicker": ".style_25 > .style_24"
};
let remaining_keys = Object.keys(return_data).length;
for (let key in return_data) {
if (return_data.hasOwnProperty(key)) {
html2json.parse(report, function () {
return this.map(return_data[key], function ($item) {
return $item.text();
});
}).done(function (items) {
if (key === "click" || key === "clicker" || key === "sent_mails") {
items[0] = items[0].replace(/,/g, "");
return_data[key] = parseInt(items[0]);
} else if (key === "openning") {
items[items.length - 2] = items[items.length - 2].replace(/,/g, "");
return_data[key] = parseInt(items[items.length - 2]);
} else if (key === "openner") {
items[items.length - 3] = items[items.length - 3].replace(/,/g, "");
return_data[key] = parseInt(items[items.length - 3]);
} else if (key === "cancel_subscription_click") {
return_data[key] = parseInt(items[13]) + parseInt(items[18]) + parseInt(items[23]);
} else if (key === "cancel_subscription_clicker") {
return_data[key] = parseInt(items[11]) + parseInt(items[16]) + parseInt(items[21]);
} else {
return_data[key] = items[0];
}
remaining_keys--;
if (remaining_keys === 0) {
return_data["click"] -= return_data["cancel_subscription_click"];
return_data["clicker"] -= return_data["cancel_subscription_clicker"];
delete return_data.cancel_subscription_click;
delete return_data.cancel_subscription_clicker;
logger.debug(return_data);
return return_data;
}
}, function (err) {
// Handle error
});
}
}
}
The execution would be function -> object init -> wait for loop to update object -> return object
You can either use a callback function or write this function as a promise.
Callback is a function you will pass into your function to execute after the data is done.
For the callback function:
https://developer.mozilla.org/en-US/docs/Glossary/Callback_function
reportParser = (report, callback) => {
//... process data
html2json.parse(report, function() {
//...
}).done(function(items) {
//after you have done process and get return_data, use callback
callback(return_data);
})
}
So when you using reportParser:
reportParser(report, function(return_data) {
//whatever you want to do with return_data
})
For the promise:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
reportParser = (report) => {
return new Promise(function(resolve, reject) {
//... process data
html2json.parse(report, function() {
//...
}).done(function(items) {
//after you have done process and get return_data, use callback
resolve(return_data);
})
})
}
And when you using reportParser function:
reportParse(report).then(return_data => {
//whatever you want to do with return_data
})
Seems html2json.parse return a Promise, so in your case you have to return a Promise as well in your parent function
reportParser: (report) => {
const return_data = {
"click": "[id^='AUTOGENBOOKMARK_75_'].style_12",
"clicker": "[id^='AUTOGENBOOKMARK_74_'].style_12",
"openning": ".style_25 > .style_24",
"openner": ".style_25 > .style_24",
"name": "[id^='AUTOGENBOOKMARK_7_'].style_12",
"subject": "[id^='AUTOGENBOOKMARK_9_'].style_12",
"audience": "[id^='AUTOGENBOOKMARK_11_'].style_12",
"sent_mails": "[id^='AUTOGENBOOKMARK_20_'].style_12",
"send_date": "[id^='AUTOGENBOOKMARK_32_'].style_12",
"cancel_subscription_click": ".style_25 > .style_24",
"cancel_subscription_clicker": ".style_25 > .style_24"
};
let remaining_keys = Object.keys(return_data).length;
/* here return a Promise */
return new Promise((resolve, reject) => {
for (let key in return_data) {
if (return_data.hasOwnProperty(key)) {
html2json.parse(report, function () {
return this.map(return_data[key], function ($item) {
return $item.text();
});
}).done(function (items) {
if (key === "click" || key === "clicker" || key === "sent_mails") {
items[0] = items[0].replace(/,/g, "");
return_data[key] = parseInt(items[0]);
} else if (key === "openning") {
items[items.length - 2] = items[items.length - 2].replace(/,/g, "");
return_data[key] = parseInt(items[items.length - 2]);
} else if (key === "openner") {
items[items.length - 3] = items[items.length - 3].replace(/,/g, "");
return_data[key] = parseInt(items[items.length - 3]);
} else if (key === "cancel_subscription_click") {
return_data[key] = parseInt(items[13]) + parseInt(items[18]) + parseInt(items[23]);
} else if (key === "cancel_subscription_clicker") {
return_data[key] = parseInt(items[11]) + parseInt(items[16]) + parseInt(items[21]);
} else {
return_data[key] = items[0];
}
remaining_keys--;
if (remaining_keys === 0) {
return_data["click"] -= return_data["cancel_subscription_click"];
return_data["clicker"] -= return_data["cancel_subscription_clicker"];
delete return_data.cancel_subscription_click;
delete return_data.cancel_subscription_clicker;
logger.debug(return_data);
/* RESOLVE THE PROMISE */
resolve(return_data);
return return_data;
}
}, function (err) {
// Handle error
/* REJECT THE PROMISE ON ERROR */
reject(err);
});
}
}
});
}
than you will able to use your function as a regular Promise
reportParser(report).then((data) => {
// Work with the returned data
}).catch((err) => {
// Handle errors
})

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