This problem might seem like a duplicate of many others, but I can't find my mistake anywhere.
The problem is that async.each throws "Callback was already called". Here's the snippet (I named the async callback done, so it doesn't get confused with the other callbacks in my code):
async.each(this.requirements, (requirement, done) => {
// That thing here passes the result as a callback
requirement.callback((result) => {
if (!result) {
// requirement not passed -> return error
done(true); // LINE 42
} else {
done(); // LINE 44
}
}, data, params, bot);
}, (err) => { // 'done' callback
log.info('handler',
`Handler '${this.label}' ${err ? 'failed' : 'succeeded'}`);
// if any requirement did not pass, do not execute handler callback
if (!err) this.callback(data, params, bot);
});
Here's the stack trace of it:
C:\Users\samuel\Code\node\sk22tgjs\node_modules\async\dist\async.js:837
if (fn === null) throw new Error("Callback was already called.");
^
Error: Callback was already called.
at C:\Users\samuel\Code\node\sk22tgjs\node_modules\async\dist\async.js:837:34
at requirement.callback (C:\Users\samuel\Code\node\sk22tgjs\node_modules\telegramjs\core\handler.js:44:11)
at Requirement.exports.command.Requirement.callback (C:\Users\samuel\Code\node\sk22tgjs\node_modules\telegramjs\telegram\requires.js:21:5)
at async.each (C:\Users\samuel\Code\node\sk22tgjs\node_modules\telegramjs\core\handler.js:39:19)
at C:\Users\samuel\Code\node\sk22tgjs\node_modules\async\dist\async.js:2953:18
at replenish (C:\Users\samuel\Code\node\sk22tgjs\node_modules\async\dist\async.js:872:19)
at C:\Users\samuel\Code\node\sk22tgjs\node_modules\async\dist\async.js:878:27
at C:\Users\samuel\Code\node\sk22tgjs\node_modules\async\dist\async.js:840:18
at requirement.callback (C:\Users\samuel\Code\node\sk22tgjs\node_modules\telegramjs\core\handler.js:44:11)
at Requirement.callback (C:\Users\samuel\Code\node\sk22tgjs\node_modules\telegramjs\core\requires.js:19:5)
The interesting thing is that the issue only occurs if done(true) gets called. Nevertheless, the error occurs at line 44, not 42.
You can also see the corrupt code on GitHub, especially the testing branch: https://github.com/22sk/telegramjs
Thanks in advance.
When a command isn't found, both the success and failure are being called rather than just the failure.
https://github.com/22sk/telegramjs/blob/5b85f04fe890a8fd32b373edb97bfebc923156b1/bot/telegram/requires.js
exports.command = new Requirement({
label: 'command',
requires: requires.has('message', 'text'),
callback: (result, data, params, bot) => {
const command = new Command(data.message.text);
if (!command.valid || command.bot && bot.me.username !== command.bot) {
// command is not valid or not meant to be handled by this bot
result(false); // <--- ### Missing return ### --->
}
// command is valid and should be handled by this bot
// write command into data
params.command = command;
result(true);
}
});
Related
Firstly, thanks in advance for taking your time to read this.
I've been working on a cloud function for my Firebase app and have been encountering some frustrating situations. Half of the time, my function works as expected; I see all expected logs and the DB gets updated correctly. The other half of the time, the function almost seems to stop midway through; only some of the logs appear, and only half of the expected DB updates are made. I've been debugging this for quite a while and am running out of ideas.
One thing that I've been seeing consistently in the logs is the following error:
Function returned undefined, expected Promise or value
I'm not sure if this error is what's causing the inconsistencies noted above, but it's the only lead I have at the moment. At this point, I've commented out nearly all the code in my cloud function and the error seems to be caused by the use of an async.auto function. As soon as I remove any reference to async, the error goes away.
To summarize my questions:
1 - Why is the use of async.auto causing the above error?
2 - Is the error the reason that there are inconsistencies with the outcome of running my cloud function?
For reference, here is my now overly-simplified and pointless function that throws the above error:
exports.updateLeaderboard = functions.database.ref('/contests/{dateString}/ladder/dayIsComplete').onWrite((event, context) => {
const isComplete = event.after._data,
contestType = 'ladder',
dateString = context.params.dateString;
if (isComplete !== true) {
console.warn(`${contestType} for ${dateString} is not yet complete.`);
return false;
}
async.auto({
fetchWinningPicks: cb => {
return cb();
},
// ... Other stuff that I've now commented out
}, err => {
if (err) {
return false;
} else {
return true;
}
});
};
I was able to resolve the error by wrapping my async.auto flow in a new Promise() and resolving/rejecting in my final callback:
exports.updateLeaderboard = functions.database.ref('/contests/{dateString}/ladder/dayIsComplete').onWrite((event, context) => {
const isComplete = event.after._data,
contestType = 'ladder',
dateString = context.params.dateString;
if (isComplete !== true) {
console.warn(`${contestType} for ${dateString} is not yet complete.`);
return false;
}
return new Promise((resolve, reject) => {
async.auto({
fetchWinningPicks: cb => {
return cb();
},
// ... Other stuff that I've now commented out
}, err => {
if (err) {
reject();
} else {
resolve();
}
});
});
};
This appears to fix both of my issues above.
What version of async are you using?
2.6.1
Which environment did the issue occur in (Node version/browser version)
8.11.3
What did you do? Please include a minimal reproducible case illustrating the issue.
Assuming fileObj is supplied from outside:
async.auto({
download: (downloadCB) => {
if (fileObj) {
fs.writeFile(__dirname + ‘fileNew.txt’, fileObj.content, 'base64', function (err) {
if (err){
return downloadCB(err);
}
return downloadCB(null , fileObj.generatedFileName); // works fine
});
} else {
let err = new Error('File not found');
return downloadCB(err);
}
},
collectData: ['download', async (results, collectCB) => {
console.log(typeof collectCB); // prints undefined
console.log(typeof results); // prints correct object
let res = await anHttpRequest();
if (res.response && res.response.statusCode == 200) {
return collectCB(null , 'fileCombined.txt'); // This is where the ISSUE happens
}
else if(res.response.statusCode >= 300) {
return collectCB(new Error('Request failed inside async-auto'));
}
}],
filterData: ['collectData', (results, filterCB) => {
doFilter(results.collectData, filterCB);
}],
})
What did you expect to happen?
After collectData finishes execution, filterData should begin execution the param passed inside collectCB function
What was the actual result?
TypeError: collectCB is not a function.
The same code executes well with version 2.0.1 but after upgrade to 2.6.1 it has stopped working and its critical for us. Any work arounds will also be appreciated.
based on the documentation (quoted in the other answer already but here it is again)
Wherever we accept a Node-style async function, we also directly accept an ES2017 async function. In this case, the async function will not be passed a final callback argument, and any thrown error will be used as the err argument of the implicit callback, and the return value will be used as the result value. (i.e. a rejected of the returned Promise becomes the err callback argument, and a resolved value becomes the result.)
what you would do is
async.auto({
download: (downloadCB) => {
if (fileObj) {
fs.writeFile(__dirname + ‘fileNew.txt’, fileObj.content, 'base64', function(err) {
if (err) {
return downloadCB(err);
}
return downloadCB(null, fileObj.generatedFileName); // works fine
});
} else {
let err = new Error('File not found');
return downloadCB(err);
}
},
// Note, no callback as per documentation
collectData: ['download', async (results) => {
console.log(typeof results); // prints correct object
let res = await anHttpRequest();
if (res.response && res.response.statusCode == 200) {
// this return is equivalent to callback(null, value);
return 'fileCombined.txt';
} else if (res.response.statusCode >= 300) {
// this throw is equivalent to callback(err);
throw new Error('Request failed inside async-auto');
}
// but surely something should be here!? for status code 201-209?
}],
filterData: ['collectData', (results, filterCB) => {
doFilter(results.collectData, filterCB);
}],
})
Just a copy-paste from the official documentation:
Wherever we accept a Node-style async function, we also directly
accept an ES2017 async function. In this case, the async function will
not be passed a final callback argument, and any thrown error will be
used as the err argument of the implicit callback, and the return
value will be used as the result value. (i.e. a rejected of the
returned Promise becomes the err callback argument, and a resolved
value becomes the result.)
How can i trigger error object in lambda.invoke
lambda.invoke(params, (err, data) => {
if (err) {
reject(.... // I would have expected below error to show up here
else
// error shows up inside the data.Payload
const result = data.Payload
// I have to create a condition to check for the error
resolve(result);
in the called lambda, i've tried the following:
exports.handler = ( event, context, callback) => {
if (payload === '')
context.done(new Error('my error message');
}
however, the error object ends up in the payload, where I have to check for it instead of going into a catch or other error path.
Instead of using the "older way" to stop execution you should use the callback(error, [success]) method. So in your lambda being invoked try callback('my error message') and that should go into your if block. You can read the documentation here for more info. I believe that in the "older way" of doing things context.done() is considered successful and context.fail() was used to signify an error.
I'm using Hapi Js and Sequelize to do something about my API and in this case I need to check everything first before go the next step.
This is my code
return promise.map(array, function (values) {
models.Goods.find({
where: {
id: values.id
}
}).then(function (res) {
if (!res || res.length === 0) {
throw new Error("Item not found");
}
});
}).then(function (res) {
//do something after check
//next step
}).catch(function(error) {
console.log(error.message);
});
I need to check if the id is in my database or not before go the next step but in this code if there is any error the throw new Error("Item not found"); never go to the catch function so I try to do something to get the error function. I changed the code inside the promise.map, I put catch function in models.Goods and console.log the error, the error shows up but the promise.map still running and go to the //next step section and not stop.
Please help me how to break the promise.map if there is an error in models.Goods
Thank you
I think you only have forgotten to return the models, this
return promise.map(array, function (values) {
models.Goods.find({
where: {
should be:
return promise.map(array, function (values) {
return models.Goods.find({
where: {
you could omit the return key word if using arrow functions.
Here is an example, I also put in some object destructuring.
return promise.map(array, ({id}) =>
models.Goods.find({
where: {id}
}).then(res => {
if (!res || res.length === 0) {
throw new Error("Item not found");
}
}) // can't have ; here now
).then(res => {
// do something after check
// next step
}).catch(error => {
console.log(error.message);
});
When the user is not found the query itself was successful, so the success callback is triggered. But since nothing matched your query, null is returned. Which is why its not triggering an error in the first place. As for the second part.
You cannot catch an error thrown in an asynchronous callback function using promises, since its context will be lost.
Using promises, the correct solution will be to reject the wrapping promise.
Promise.reject(new Error('fail')).then(function(error) {
// not called
}, function(error) {
console.log(error); // Stacktrace
});
The question:
I'm using Chai to do the tests and I seem to be stuck on testing an expected error:
Chai expected [Function] to throw an (error)
Current code:
Here's the code of the test:
describe('Do something', function () {
it('should remove a record from the table', function (done) {
storage.delete(ID, done);
});
it('should throw an error when the lookup fails', function () {
expect(storage.delete.bind(storage, ID)).to.throw('Record not found');
});
});
Here's the code of the function:
delete: function (id, callback) {
// Generate a Visitor object
visitor = new Visitor(id);
/* Delete the visitor that matches the queue an
cookie provided. */
tableService.deleteEntity(function (error, response) {
// If successful, go on.
if (!error) {
// Do something on success.
}
// If unsuccessful, log error.
else {
if (error.code === 'ResourceNotFound') {
throw new Error('Record not found');
}
// For unexpected errros.
else {
throw new Error('Table service error (delete): ' + error);
}
}
if (callback) callback();
});
},
Attempted solutions:
I've tried multiple variations of calling expect function (including calling anonymous function:
expect(function() {storage.delete(ID);}).to.throw('Record not found');
Bind, as provided in the example,
and the basic one of
expect(storage.delete(ID)).to.throw('Record not found');
I've also tried substituting the throw parameter from 'Record not found' to multiple things including directing the input to an already created error (Error), and creating a new error in the parameter (new Error('Record not found'));
Possible causes:
I have a suspicion that the error is not being thrown because it takes a while for the test to communicate with the database to delete the record, however I am not sure of how I could remedy that.
Additionally, it seems that the test that runs right after this one actually returns the error that was supposed to be returned on THIS test.
Given (from comments) that tableService.deleteEntity is asynchronous, it is impossible to test that throw. And the code itself is invalid. Because the thrown exception won't be caught, it will be unhandled as it was thrown in a different tick. Read more about Asynchronous error handling in JavaScript and unhandled exceptions in Node.js
In other words such a function cannot be tested for throwing errors:
function behaveBad(){
setTimeout(function(){
throw new Error('Bad. Don\'t do this');
}, 50);
}