Failing test - Mocha's done() called multiple times - javascript

I've tried looking at topics with a similar error but could not fit those solutions into the context of my issue.
When I try to run the the following test (function included that is tested):
function myFunc(next, obj) {
const pairs = {};
obj.listing.forEach((element) => {
if (element.x in pairs && pairs[element.x] !== element.y) {
const err = new Error('This was not ok');
next(err);
} else {
pairs[element.x] = element.y;
}
});
next();
}
it('should fail as 9 has been changed to 5 in the second object of the listing', function (done) {
const callback = (err) => {
if (err && err instanceof Error && err.message === 'This was not ok') {
// test passed, called with an Error arg
done();
} else {
// force fail the test, the `err` is not what we expect it to be
done(new Error('Assertion failed'));
}
}
myFunc(callback, {
"listing": [
{ "x": 5, "y": 9 },
{ "x": 5, "y": 11 }
]
});
});
I get this error:
What is the cause of this and how can I fix it?

You need to add a return in the if block of your myFunc so that the callback function next is called only once and indeed the done() callback in the main test case:
function myFunc(next, obj) {
const pairs = {};
obj.listing.forEach((element) => {
if (element.x in pairs && pairs[element.x] !== element.y) {
const err = new Error('This was not ok');
return next(err);
} else {
pairs[element.x] = element.y;
}
});
next();
}

#Ankif Agarwal's solution was not the correct one but it did point me in the right direction.
The forEach() method is not short circuited and therefor makes a call to next() more than once (Short circuit Array.forEach like calling break).
I was able to solve this in one of two way's.
By extracting the call to next() from the forEach() logic:
function myFunc(next, obj) {
const pairs = {};
let err = null;
obj.listing.forEach((element) => {
if (element.x in pairs && pairs[element.x] !== element.y) {
err = new Error('This was not ok');
} else {
pairs[element.x] = element.y;
}
});
if (err !== null) {
next(err);
} else {
next();
}
}
However this still makes the forEach() run through all element. If possible it seems better to short circuit it and break out of it soon as a violation occurs that sets the error, like so:
function myFunc(next, obj) {
const pairs = {};
const BreakException = {};
let err = null;
try {
obj.listing.forEach((element) => {
if (element.x in pairs && pairs[element.x] !== element.y) {
err = new Error('This was not ok');
throw BreakException;
} else {
pairs[element.x] = element.y;
}
});
next();
} catch (e) {
if (e !== BreakException) throw e;
next(err);
}
}
Hopefully someone can use this in the future.

Related

How to add async/await to my functions in nodejs?

I tried to make the code asynchronous but I couldn't. What i need to do?
This is my functions:
1.
router.post('/urls', (req, response) => {
count = 2;
webUrl = req.body.url;
depth = req.body.depth;
letstart(webUrl, response);
});
function letstart(urlLink, response) {
request(urlLink, function (error, res, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', res && res.statusCode); // Print the response status code if a response was received
//console.log('body:', body); // Print the HTML for the Google homepage.
if (!error) {
getLinks(body);
if (!ifFinishAll) {
GetinsideLinks(linkslinst, response);
}
else {
console.log("Finish crawl");
}
}
else {
console.log("sorry");
return "sorry";
}
});
}
function GetinsideLinks(list, response) {
count++;
if (count <= depth) {
for (let i = 0; i < list.length; i++) {
const link = list[i].toString();
var includeUrl = link.includes(webUrl);
if (!includeUrl) {
request(link, function (error, res, body) {
console.error('error2:', error); // Print the error if one occurred
console.log('statusCode2:', res && res.statusCode); // Print the response status code if a response was received
if (!error) {
getLinks(body);
}
else {
console.log("sorry2");
}
});
}
}
ifFinishAll = true;
}
else {
console.log("finish");
ifFinishAll = true;
response.status(200).send(resArray);
};
return resArray;
}
function getLinks(body) {
const html = body;
const $ = cheerio.load(html);
const linkObjects = $('a');
const links = [];
linkObjects.each((index, element) => {
countLinks = linkObjects.length;
var strHref = $(element).attr('href');
var strText = $(element).text();
var existUrl = linkslinst.includes(strHref);
var existText = textslist.includes(strText);
if (strText !== '' && strText !== "" && strText !== null && strHref !== '' && strHref !== "" && strHref !== null && strHref !== undefined && !existUrl && !existText) {
var tel = strHref.startsWith("tel");
var mail = strHref.startsWith("mailto");
var linkInStart = isUrlValid(strHref);
if (!tel && !mail) {
if (linkInStart) {
links.push({
text: $(element).text(), // get the text
href: $(element).attr('href'), // get the href attribute
});
linkslinst.push($(element).attr('href'));
textslist.push($(element).text());
}
else {
links.push({
text: $(element).text(), // get the text
href: webUrl.toString() + $(element).attr('href'), // get the href attribute
});
linkslinst.push(webUrl.toString() + $(element).attr('href'))
textslist.push($(element).text());
}
}
}
});
const result = [];
const map = new Map();
for (const item of links) {
if (!map.has(item.text)) {
map.set(item.text, true); // set any value to Map
result.push({
text: item.text,
href: item.href
});
}
}
if (result.length > 0) {
resArray.push({ list: result, depth: count - 1 });
}
console.log('res', resArray);
return resArray;
}
I want to return/response finally to the "resArray". I tried to add async and await to function number 1 and number 2 but it didn't succeed. Maybe I need to add async/await to all functions? How can I fix that?
You can achieve your goal by using async-await.
An async function is a function declared with the async keyword, and the await keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.
Basic example:
function resolveImmediately() {
return new Promise(resolve => {
resolve(true);
});
}
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveImmediately();
console.log(result);
if(result) {
const anotherResult = await resolveAfter2Seconds();
console.log(anotherResult);
}
}
asyncCall();
Note: Your code is too long to debug. As a result, to make you understand about the approach (what & how to do), i have added a simple example into my answer.

Mongoose fetching data, setting in a list (undefined)

I am trying to fetch all messages of two users from MongoDB using a custom function. I am not able to set list. I have no idea how to use callback here.
fetchall = function(chatuser, me) {
var allmessage = [] ;
var promise = this.model.find({},function(err, docs) {
if (err) { return console.error(err);
}
console.log(docs); //working
// doSomethingElse(docs);
allmessage = JSON.stringify(docs);
return allmessage;
}).exec();
// promise.then(doSomethingElse(cc));
console.log('all',allmessage); // undefined
return allmessage;
};
here is Example
fetchall = function(chatuser, me, cb) {
var allmessage = [];
var promise = this.model.find({}, function(err, docs) {
if (err) {
// here cb is callback
return cb(err)
} else {
console.log(docs); //working
if (Array.isArray(docs) && docs.length > 0) {
// do it here
cb(null, docs)
} else {
// throw error here
// no result found like
cb()
}
}).exec();
};

Why line 27 is executed before line 24?

I m beginner with javascript and try to build a clean object to use the Dockerode library in my use Case. I have an async problem here my line 27 executes before 24 and I don't understand why and how to fix it!
Also if it's easier for you, please visit this public gist: https://gist.github.com/msitruk/2cdb655a0bebdb29c61d8bc5606a2695
const Docker = require('dockerode');
const docker = new Docker({
socketPath: '/var/run/docker.sock'
});
// CONSTRUCTOR
function SearchUtils() {
this.listContainersPromise = docker.listContainers({all: true});
this.scraperListId = [];
}
// "METHODS"
SearchUtils.prototype.run = function() {
this.getScraperContainersListPromise()
.then((containers) => {
for (let i = 0; i < containers.length; i++) {
if (containers[i].Names.toString().indexOf("scraper") !== -1) {
this.addToScraperList(containers[i].Id, "wait");
}
}
}, (err)=>{console.log(err)})
.then(()=>{
this.checkReadyScraper();
},(err)=>{console.log(err)})
.then(() => {
this.scrap();
}, (err)=>{console.log(err)});
};
SearchUtils.prototype.checkReadyScraper = function() {
for (let i = 0; i < this.scraperListId.length; i++) {
this.exec("getStatus", this.scraperListId[i].id);
}
};
SearchUtils.prototype.getScraperContainersListPromise = function() {
return this.listContainersPromise; // <- Not working
};
SearchUtils.prototype.exec = function(type, containerId){
let container = docker.getContainer(containerId);
if (type === "getStatus"){
this.runExec(container, 'cat /home/immobot/status');
}
else if (type === "scrap") {
this.runExec(container, 'torify scrapy crawl seloger -o seloger.json');
}
};
SearchUtils.prototype.scrap = function() {
let localRdyScraperList = [];
for (let i = 0; i < this.scraperListId.length; i++) {
if(this.scraperListId[i].status.toString('utf8').indexOf("ready") !== -1){
localRdyScraperList.push(this.scraperListId[i].id);
}
}
console.log("test de localRdyScraperList : "+localRdyScraperList);
// this.exec("scrap", this.scraperListId[i].id);
};
SearchUtils.prototype.addToScraperList = function(containerId,status) {
this.scraperListId.push({id: containerId, status: status});
};
SearchUtils.prototype.getScraperList = function() {
return this.scraperListId;
};
SearchUtils.prototype.getScraperList = function() {
return this.scraperListId;
};
SearchUtils.prototype.runExec = function (container, cmd) {
let options = {
Cmd: [ '/bin/bash', '-c', cmd ],
AttachStdout: true,
AttachStderr: true
};
container.exec(options, (err, exec) => {
if (err) return;
exec.start((err, stream) => {
if (err){
console.log("error : "+err);
return;
}
// container.modem.demuxStream(stream, process.stdout, process.stderr)
if (cmd === "cat /home/immobot/status"){
let newStream = require('stream');
let logStream = new newStream.PassThrough();
logStream.on('data', (chunk) => {
// console.log(chunk.toString('utf8'));
if (chunk.toString('utf8').indexOf("ready") !== -1){
console.log("CONTAINER READY !!");
//EDIT CONTAINER STATUS IN SCRAPERLIST TO READY
this.changeStatusToReady(container.id);
}
});
container.modem.demuxStream(stream, logStream, process.stderr);
}
else if (cmd === "torify scrapy crawl seloger -o seloger.json"){
console.log("on lance le scrape sur un des scraper rdy");
container.modem.demuxStream(stream, process.stdout, process.stderr)
}
// container.modem.demuxStream(stream, logStream, process.stderr);
exec.inspect(function(err, data) {
if (err){
console.log("error : "+err);
return;
}
});
});
});
};
SearchUtils.prototype.changeStatusToReady = function (containerId){
for (let i = 0; i < this.scraperListId.length; i++) {
if(this.scraperListId[i].id === containerId){
this.scraperListId[i].status = "ready";
}
}
// console.log(this.getScraperList());
};
module.exports = SearchUtils;
If your chaining promises, don't forget to return your next promise..
eg..
.then(()=>{
this.checkReadyScraper();
}
If checkReadyScraper() is a promise, then you will want to return it.
eg.
.then(()=>{
return this.checkReadyScraper();
}
Otherwise all your doing is running checkReadyScraper() and totally ignoring the returned Promise.
Here is how I think your runExec should look. I'm assuming the exec.inspect is what you want to resolve on.
SearchUtils.prototype.runExec = function (container, cmd) {
return new Promise ((resolve, reject)=>{
let options = {
Cmd: [ '/bin/bash', '-c', cmd ],
AttachStdout: true,
AttachStderr: true
};
container.exec(options, (err, exec) => {
if (err) return reject(err); //return error
exec.start((err, stream) => {
if (err){
console.log("error : "+err);
return reject(err); //return error
}
// container.modem.demuxStream(stream, process.stdout, process.stderr)
if (cmd === "cat /home/immobot/status"){
let newStream = require('stream');
let logStream = new newStream.PassThrough();
logStream.on('data', (chunk) => {
// console.log(chunk.toString('utf8'));
if (chunk.toString('utf8').indexOf("ready") !== -1){
console.log("CONTAINER READY !!");
//EDIT CONTAINER STATUS IN SCRAPERLIST TO READY
this.changeStatusToReady(container.id);
}
});
container.modem.demuxStream(stream, logStream, process.stderr);
}
else if (cmd === "torify scrapy crawl seloger -o seloger.json"){
console.log("on lance le scrape sur un des scraper rdy");
container.modem.demuxStream(stream, process.stdout, process.stderr)
}
// container.modem.demuxStream(stream, logStream, process.stderr);
exec.inspect(function(err, data) {
if (err){
console.log("error : "+err);
//don't forget to return the rejection
return reject(err);
}
//looks like everything was ok, lets resolve
resolve(data);
});
});
});
//resolve("ok"); too early
// TODO ADD EROR STRATEGY
//reject("error"), pointless
});
};
Executing a task (with container.exec, on line 81 in your code), runs asynchronious from the rest of your steps, having a callback when they are done.
You would have to make sure all the checks for scrapers are finished before running the scrap command if order matters.
First - there is no need to handle errors in every then() call.
You can implement a single error catch, which will catch error in any then() item in a sequence:
.then(()=> {
this.checkReadyScraper();
})
.then(() => {
this.scrap();
})
.catch(e => console.log(e))
Also note, that arrow function like catch(e => console.log(e)) doesn't require {} and ;
Your problem is that your task is Async. If you want to chain tasks - you should make a task to return a Promise
This is a rough exaple what you should refactor:
//Should return Promise
SearchUtils.prototype.exec = function(type, containerId){
let container = docker.getContainer(containerId);
if (type === "getStatus"){
//runExec should return us a Promise
return this.runExec(container, 'cat /home/immobot/status');
}
else if (type === "scrap") {
return this.runExec(container, 'torify scrapy crawl seloger -o seloger.json');
}
};
SearchUtils.prototype.runExec = function (container, cmd) {
return new Promise(function(resolve, reject) {
//do some stuff
//then call resolve(result)
//or call reject(error)
});
}
After this, you will be able to chain Promises (which is quite awesome actually and helps to solve callback hell):
.then(()=> {
//this not returns Promise, and it will be correctly chained
return this.checkReadyScraper();
})
.then(() => {
//this not returns Promise, and it will be correctly chained
return this.scrap();
})
.catch(e => console.log(e))
Also, to make this looks cleaner I even recommend to do some small refactoring, which will finally give you oneliner:
.then(this.checkReadyScraper).then(this.scrap).catch(console.log)

Loop not pushing game data to Array before console dump?

I have been looking over this code and redoing it so often trying to get this loop to work. I have also come to realise i am getting 429 error from s3 which i have to look into to. in the matter at hand, i am attempting to save the data to my gameData Array and then output via console after the promise has fulfilled so ultimately the promise variable can return the array. But i am not able to get all the data to store in the array. Something todo with the loop i guess but i cant pin point it. Any ideas?
var getGameData = function(matchIdArray) {
var promise = new Promise(function(resolve,reject) {
s3.headObject({Bucket: 'lolsearchgames', Key: '347341'}, function(err, result) {
if (err && err.code === 'NotFound') {
var gameData = new Array();
for (var i = 0; i < matchIdArray.length; i++) {
gameData.push(new Promise(function(res, rej) { lolapi.Match.get(matchIdArray[i], function (error, gamedata) {
if (error) {
rej(error);
}
if (gamedata) {
res(gamedata);
}
})})
.then(function() {
if (i === 9) {
console.log(gameData);
resolve(gameData);
}
}));
}
} else {
// Retrieve from s3 Bucket
}
})
});
return promise;
};
Here's a snippet of your code that needs to be rewritten
if (err && err.code === 'NotFound') {
var gameData = new Array();
for (var i = 0; i < matchIdArray.length; i++) {
gameData.push(new Promise(function(res, rej) {
lolapi.Match.get(matchIdArray[i], function(error, gamedata) {
if (error) {
rej(error);
}
if (gamedata) {
res(gamedata);
}
})
})
);
}
Promise.all(gamedata).then(function() {
console.log(gameData);
resolve(gameData);
});
}
This uses Promise.all on the array of gamedata promises to wait for them to all resolve before resolving the "main" promise
Note: this is a quick and dirty fix, the code may be able to be rewritten to remove the need for the outer new Promise - however, without seeing the code foe the else that's only a speculation on my part
Still "quick and dirty", but a better rewrite
if (err && err.code === 'NotFound') {
Promise.all(matchIdArray.map(function(matchId) {
return new Promise(function(res, rej) {
lolapi.Match.get(matchId, function(error, gamedata) {
if (error) {
rej(error);
}
if (gamedata) {
res(gamedata);
}
})
});
})).then(function() {
console.log(gameData);
resolve(gameData);
});
}

how to use js Promise to eliminate pyramid of doom

i'm trying to understand how to use js Promise api to refractor a code that has lots of nested IF.
example when getting JSON object from localstorage a normal code would look like
function $storage(key,default) {
let json = localStorage.getItem(key);
if(json === null) return default;
try{ // <-- need try catch in case value was not valid json object
json = JSON.parse(json);
} catch (e) {
json = default;
}
return typeof json === 'object' ? json : default;
}
the readibility of this code is not that good. so i thought may be i can utilize js Promise to rewrite it into
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then( ls => JSON.parse(ls.getItem(key)) )
.then( json => typeof json === 'object' ? json : HOW_TO_THROW_ERROR() )
//on more validation step if needed
.then( json => typeof json === 'object' ? json : HOW_TO_THROW_ERROR() )
.then( valid_json => { return = valid_json } )
.catch( error => { ret = default; console.warn('json invalid',e); } );
return ret;
}
now i want to know how can i throw an exception inside then so that the catch can caught it and execute default ?
is this valid usage of js promise of am i wasting performance
You could use Promise.reject() to throw an error:
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then( ls => JSON.parse(ls.getItem(key)) )
.then( json => typeof json === 'object' ? json : Promise.reject("invalid json") )
.then( valid_json => { return = valid_json } )
.catch( err => { ret = default; console.warn(err.message); } );
return ret;
}
Although I find the following more legible and idiomatic.
function $storage(key,default) {
let json = localStorage.getItem(key);
if(json === null || typeof json !== 'object') json = default;
try{
json = JSON.parse(json);
} catch (e) {
json = default;
} finally {
return json
}
}
Promises are used, as you surely know, for asynchronous computation. Any other use might confuse other programmers.
You can use thrown to thrown the errors and then handle them in catch method
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success!"
throw 'oh, no!';
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
// The following behaves the same as above
p1.then(function(value) {
console.log(value); // "Success!"
return Promise.reject('oh, no!');
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
But if thrown some errors in async functions the catch is never called.
// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // This is never called
});
source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
in JavaScript you can use the keyword throw to throw any error.
Examples from MDN:
throw "Error2"; // generates an exception with a string value
throw 42; // generates an exception with the value 42
throw true; // generates an exception with the value true
throw new Error("Error");
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then( ls => JSON.parse(ls.getItem(key)) )
.then( json => typeof json === 'object' ? json : throw new Error("invalid json") )
//on more validation step if needed
.then( json => typeof json === 'object' ? json : throw new Error("invalid json") )
.then( valid_json => { return = valid_json } )
.catch( err => { ret = default; console.warn(err.message); } );
return ret;
}
You could basically just do the following, because if the parse fails, it will be catched automatically.
function $storage (key, default) {
let ret;
let promise = new Promise( (y,n) => y(localStorage) )
.then(ls => JSON.parse(ls.getItem(key)) )
.then(valid_json => { return = valid_json } )
.catch(err => { ret = default; console.warn(err.message); } );
return ret;
}
The problem I see is just about JSON.parse, wrapping it in a more usable function you get something like:
function safeParse(x) {
try {
return JSON.parse(x);
} catch(e) {
// Log the problem
return null;
}
}
function parmval(key, defval) {
var json = safeParse(localStorage.get(key));
return (typeof json === "object") ? json : defval;
}
Promises are about asynchronous operations, not IFs.

Categories

Resources