I have a router that needs to be able to call both async functions as well as sync functions. I have set up the following code as the executor:
async exec(method, path, params, payload) {
const route = this.get(method, path);
if (!route) throw new RequestError(422, 'Route [' + path + '] not found');
// Recheck, just to make sure
if (typeof route._callback !== 'function')
throw new AppError('Callback defined for route [' + route.name + '] is not a function');
this._logger('info', 'Route [' + path + '] recieved. Executing [' + route.callback + ']', route.params);
let results;
try {
results = route._callback(Object.assign(params || {}, route.params || {}), payload);
// If the results is a promise, then we await the promise results
if (results.then && typeof results.then === 'function') {
await results;
}
} catch (err) {
throw new AppError(err.message, err);
}
return results;
}
In the calling function, I have several functions which have two different potential areas where an error can be thrown. One is outside of a Promise.All and the other is inside.
async myFunction(params,payload) {
// This function can throw an error if the database
// connection goes bad
if( await this._checkExists(this.conn, params) )
return { code: 2, message: 'Already Exists' };
if ((await this._regCount(this.conn, params)) === 0) {
Promise.all([
this._sendMail(params),
this._notify(params),
this._update(params)
});
return { code: 1, message: 'Success' };
}
}
Because the router is part of a library and the myFunction is a user function that I don't have control over, I would like the router to be able to catch any exceptions that occur within myFunction (or any of its inner functions). I don't want the user to be forced to write try/catch blocks internally. I want them to bubble up to the router's internal error reporting functions.
With the current code structure, if I throw an error within myFunction, then the catch in the router's exec function works fine. However, if an error is thrown within any of the internal functions (i.e. _checkExists or _sendMail), etc, then I get the UnhandledRejection error.
What is the proper way to trap for this within the router's exec function?
The router code is fine. I ended up re-structuring the myFunction to be:
async myFunction(params,payload) {
// This function can throw an error if the database
// connection goes bad
if( await this._checkExists(this.conn, params) )
return { code: 2, message: 'Already Exists' };
if ((await this._regCount(this.conn, params)) > 0)
return { code: 1, message: 'Already Exists' };
Promise.all([
this._sendMail(params),
this._notify(params),
this._update(params)
});
return { code: 1, message: 'Success' };
}
Which I am not exactly sure why it would make a difference. Granted, the above code would have not returned anything if there was an exception thrown in regCount, but not sure why that would have bubbled up into an UnhandledRejection.
Problem has been resolved, but the async/await seems rather finicky that if you don't get it just right, you can run into problems with dealing with promises.
RANT: If ECMA would just solve the core issue of being able to run multithreaded instead of trying to make everything work in a single thread, we wouldn't have to go through all this async pain. Give us threads and we can choose where and when we want sync versus async.
....ok, stepping down from my soapbox.
Related
class AuthController {
static methods = {
GET: {
'/auth/signup': {
func: AuthService.signUp,
response: (data, res) => {
res.statusCode = 200;
res.end(JSON.stringify(data));
},
},
},
};
static use(req, res) {
const route = this.methods[req.method][req.url];
if (!route) {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Not found 404!' }));
return;
}
try {
const data = JSON.parse(req?.body?.data || '{}');
const result = route.func({ ...data });
route.response(result, res);
} catch (err) {
console.log(err, 'here');
res.statusCode = err.statusCode || 500;
res.end(JSON.stringify(err.message));
}
}
}
class AuthService {
static async signUp({ login, password }) {
if (!login || !password) throw new BaseError(400, 'kl', 'Custom error');
}
}
It shows the error in console but try catch block doesn't see it.
Here is the traceback.
I don't know what the reason is because the function which throws error is inside of the block. Help please!
The trace back that you attached tells you exactly what the problem is and what you need to do:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
You can't catch an exception thrown by an async function with a try..catch block outside of that function, because script execution reaches the catch block before the async execution is finished. You therefor have to use .catch(..) instead:
const result = route.func({ ...data }).catch((err) => {
console.log("catched error: ", err);
});
I see one issue. You have declared signUp() to be async. That means it always returns a promise and it means that any throw operations inside it reject that promise that it returns (the exception doesn't propagate synchronously). But, when you attempt to call it here:
const result = route.func({ ...data });
You don't await it so when signUp() rejects, the promise goes into result, but nobody ever handles the fact that the promise rejected and you get UnhandlePromiseRejectionWarning from the system.
I can't see the whole overall design (of all the other routes), but perhaps you just need to add await to this:
const result = await route.func({ ...data });
And, you would have to make .use() be async also.
Or, if signUp() doesn't actually need to be async, then just remove the async from its declaration and the throw will be synchronous (instead of being turned into a rejected promise) and your try/catch will catch it then.
I am struggling with some code... The 2 examples below I would think would work the same but the second example throws an error? I am also struggling to figure out the error, it's not bubbling up? Admittedly I am not a seasoned node developer so any guidance would be much appreciated! If it's relevant the create method in the module is calling the sequelize create.
This works
var p1 = deliverabiltyConfigs.create2(cfgObject);
return Promise.all([p1]).then(function([res1]) {
res.json({result: res1})
});
This does not
deliverabiltyConfigs.create2(cfgObject).then(res1 =>{
res.json({result: res1})
})
Here is the function that I am calling in a controller module
exports.create2 = (dConfig) => {
DeliverabilityConfig.create(dConfig)
.then(data => {
return data
})
.catch(err => {
return {
message:
err.message || "Some error occurred while createing this config."
};
});
};
The create2 function always returns null, so neither invocation will work. Promise.all([p1]) hides the problem, returning a promise to perform an array of no promises.
create2(cfgObject).then(res1 =>{ attempts to invoke then() on null, generating a more obvious error. But neither way works.
Fix by deciding which promise syntax you want, using each as follows:
Using original promise syntax....
exports.create2 = dConfig => {
// note the return
return DeliverabilityConfig.create(dConfig)
.catch(err => {
const message = err.message || "Some error occurred while createing this config.";
return { message };
});
};
// caller
deliverabiltyConfigs.create2(cfgObject).then(result =>{
res.json(result);
})
With recent syntactic sugar...
exports.create2 = async (dConfig) => {
try {
// its fine to not await here, since the caller will await
// but just to illustrate how you might perform more async work here...
return await DeliverabilityConfig.create(dConfig);
} catch (err) {
const message = err.message || "Some error occurred while createing this config."
return { message }
}
}
// caller
var result = await deliverabiltyConfigs.create2(cfgObject);
res.json(result);
Use Promise.all() to run >1 promise concurrently. You've only got one promise in the OP, so no reason for it here.
I was writing a script to pull data from Google Cloud metrics via API when I accidentally discovered that I don't know how to properly catch errors of asynchronous functions. :O
Here is the example code from google cloud:
// Imports the Google Cloud client library
const monitoring = require('#google-cloud/monitoring');
// Creates a client
const client = new monitoring.MetricServiceClient();
/**
* TODO(developer): Uncomment and edit the following lines of code.
*/
const projectId = 'XXXXXXXXX';
async function getMetrics() {
const request = {
name: client.projectPath(projectId),
filter: 'metric.type="cloudsql.googleapis.com/database/cpu/utilization"',
interval: {
startTime: {
// Limit results to the last 20 minutes
seconds: Date.now() / 1000 - 60 * 1,
},
endTime: {
seconds: Date.now() / 1000,
},
},
// Don't return time series data, instead just return information about
// the metrics that match the filter
view: 'HEADERS',
};
// Writes time series data
console.log('start')
const [timeSeries] = await client.listTimeSeries(request);
console.log('Found data points for the following instances:');
timeSeries.forEach(data => {
console.log(data.metric.labels.instance_name);
});
}
getMetrics();
The function listTimeSeries returns a promise. I got an error that I need to be authenticated to perform that action, no problem there.
The issue is that I couldn't catch that error.
I tried surrounding the call with try {...} catch (err) {...} block, wasn't caught.
I tried to catch it like this const [timeSeries] = await client.listTimeSeries(request).catch(console.log); - No luck there.
I must be missing something because I'm pretty new to nodeJS and no way catching errors from async functions is not supported.
I'm using nodeJS v14.
What am I missing guys?
Thank you in advance!
EDIT
As requested (by #CherryDT), here is the full error output:
I hope its not too blurry.
EDIT
It turns out that the way I've been trying to catch errors is fine.
The issue occurred because of listTimeSeries function (from an external library), which threw an error instead of rejecting the promise, which is impossible to catch.
Thanks, guys.👍
Note that I refer to "async functions" and "asynchronous functions." In Javascript "async function" means a function created with the async keyword, whereas when I say "asynchronous function" I mean in the traditional sense, any function that runs asynchronously. In Javascript, functions created with the async keyword are actually just promises under the hood.
Your code would work if errors thrown from asynchronous functions (inside promises) could be caught. Unfortunately, they can't. Unless the function is using the async function syntax, errors in promises must be wrapped with reject. See the MDN example for the gotcha we're looking at here:
// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
throw new Error('Uh-oh!');
});
p1.catch(function(e) {
console.error(e); // "Uh-oh!"
});
// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw new Error('Uncaught Exception!');
}, 1000);
});
p2.catch(function(e) {
console.error(e); // This is never called
});
// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
resolve();
throw new Error('Silenced Exception!');
});
p3.catch(function(e) {
console.error(e); // This is never called
});
I believe this is the code in the library that's throwing the error, below. Notice that another error is being properly rejected. All comments are mine.
for (const methodName of metricServiceStubMethods) {
const callPromise = this.metricServiceStub.then(
stub => (...args: Array<{}>) => {
if (this._terminated) {
// This is the right thing to do!
return Promise.reject('The client has already been closed.');
}
const func = stub[methodName];
return func.apply(stub, args);
},
(err: Error | null | undefined) => () => {
// If this was an async function (as in, using the keyword async,
// not just literally an asynchronous function), this would work,
// because the async keyword is just syntactic sugar for creating
// a promise. But it's not so it can't be caught!
throw err;
}
);
I believe, in this case, unfortunately there's no way for you to catch this error.
You can do this.
(async function() {
try {
await getMetrics();
} catch(error) {
console.log("Error occured:", error);
}
})();
Please note that if you are trying to catch the error in Promise you can use .then(() => { }).catch(err => { }) style, but for async/await you will need try { } catch(err) { } style to catch the error.
Edit
By doing this, it must catch any errors if the promise become rejected. If you still cannot catch the error, this means that the library you're using doesn't reject the promise properly (Promise.reject()), instead it did hard-coded throw error inside the promise instead of rejecting one. For this case you can't do anything with error catching.
I am stucking for 3 hours on this topic. I dont find a solution how to test the if(err) branch in this code:
function createFile(data){
return new Promise(function(resolve, reject) {
try {
if(data === null || data === undefined){
throw new Error(errorMessages.noDataDefined);
}
let internalJobId = uuid.v4();
let fileName = 'project_name' + internalJobId + '.xml';
fs.writeFile(config.tmpPath + fileName, data, function (err) {
if (err){
throw new Error(err.toString());
} else {
resolve(fileName);
}
});
} catch (error) {
return reject(error);
}
});
}
This test passes but it does not call the if (err) { throw new Error(err.toString())}
I have to find a solution, how the callback returns an error, but I dont get the right solution.
test('Error', () => {
jest.mock('fs', () => ({
writeFile: jest.fn((path, data, callback) => callback(Error('some error')))
}));
return expect(createFile('Does not matter')).rejects.toThrow('some error');
});
But with this test there is even not a reject, so there is never thrown an error. I would appreciate if anyone could help me out there.
There are two problems here. One is that fs.writeFile isn't correctly mocked. Another is that createFile doesn't correctly handle errors and can't meet the expectation.
jest.mock affects modules that haven't been imported yet and hoisted to the top of the block (or above imports when used at top level). It cannot affect fs if a module that uses it has already been imported. Since fs functions are commonly used with their namespace, they can also be mocked as methods.
It should be either:
// at top level
import fs from 'fs';
jest.mock('fs', ...);
...
Or:
// inside test
jest.spyOn(fs, 'writeFile').mockImplementation(...);
...
And be asserted to make the test more specific:
expect(fs.writeFile).toBeCalledTimes(1);
expect(fs.writeFile).toBeCalledWith(...);
return expect(createFile('Does not matter'))...
Promise constructor doesn't need try..catch because it already catches all synchronous error inside it and cannot catch asynchronous errors from callbacks. For places where a promise needs to be rejected, reject can be preferred for consistency.
That an error is thrown inside fs.writeFile callback is a mistake and results in pending promise. It has no chance to reject the promise, has no chance to be caught with try..catch outside the callback and causes uncaught error.
It should be:
function createFile(data){
return new Promise(function(resolve, reject) {
if(data === null || data === undefined){
reject(new Error(errorMessages.noDataDefined));
}
let internalJobId = uuid.v4();
let fileName = 'project_name' + internalJobId + '.xml';
fs.writeFile(config.tmpPath + fileName, data, function (err) {
if (err){
reject(new Error(err.toString()); // reject(err) ?
} else {
resolve(fileName);
}
});
});
}
In order to keep nesting to minimum, parts that don't need promisification can be moved outside the constructor with the function being async:
async function createFile(data){
if(data === null || data === undefined){
throw new Error(errorMessages.noDataDefined);
}
return new Promise(function(resolve, reject) {
let internalJobId = uuid.v4();
...
There is also fs.promises API that may not need to be promisified.
Also notice that new Error(err.toString()) may be unnecessary and result in unexpected error message and so fail the assertion. The promise can be rejected with err as is. If the purpose is to remove unnecessary error information or change error stack, it should be new Error(err.message).
The solution was:
jest.spyOn(fs, 'writeFile').mockImplementation((f, d, callback) => {
callback('some error');
});
Thanks to Estus Flask!
The following three lines of code did the job for me:
import * as fs from 'fs/promises';
jest.mock('fs/promises');
jest.spyOn(fs, 'writeFile').mockImplementation( your implementation here );
export function getAllHost(req, res) {
function findAllHost() {
let query = {};
query.company = req.query && req.query.companyId ? req.query.companyId : res.locals.payload.companyId;
query.active = true;
if(req.query && req.query.name) {
let regexp = new RegExp('\\b' + req.query.name);
query['name'] = {$regex: regexp, $options: 'i'};
}
return Host.find(query);
}
async function sendRes() {
let allHost = [];
let hosts = [];
try {
hosts = await findAllHost();
} catch(err){
console.log(err)
}
for (let host of hosts) {
allHost.push(new Host_V1(host));
}
return allHost
}
sendRes().then(data => res.status(200).json(data)).catch(error => {
console.log("error is", error);
res.status(error.status || 500).json({
message: error.status ? error.message : 'Server Error'
});
});
}
I have been trying to adapt async/await into my code, so I converted one of the Promise based api controller to make use of async/await, but the thing that bothers me is that my server responds with 500, and the console.log inside my catch block doesn't print anything.
No error gets thrown.
I am using babel babel-plugin-syntax-async-functions to parse it.
What is it that I am doing wrong?
Your code is a bit overcomplicated, but judging by it you should receive an error in the console if one appears. It could instead be that you have a middleware producing an error? The main issue is that you're catching the error in the async function sendRes, so the .catch-method you call on the returned Promise will never be fired even if there is an error.
Like many others that are new to async/await, you've misunderstood and believe that you have to wrap every await expression in a try/catch-block. This is not the case. The error "trickles" up the call chain, and unless a particular function can provide a different return value, it's best to catch the error from the top-most callee. Take this simple example which shows a common anti-pattern: https://repl.it/repls/PunySafeInterfacestandard (await and async isn't even needed in these examples, but I added them for clarity)
But if you try to simplify your code, maybe something like the below snippet, you might be able to rule out if it's a middleware or not.
export async function getAllHosts (req, res) {
try {
let query = {
company: req.query && req.query.companyId ? req.query.companyId : res.locals.payload.companyId,
active: true
}
if (req.query && req.query.name) {
let regexp = new RegExp('\\b' + req.query.name)
query.name = {$regex: regexp, $options: 'i'}
}
let allHosts = (await Host.find(query)).map((host) => new Host_V1(host))
return res.json(allHosts)
} catch (e) {
console.log("error is", e)
res.status(error.status || 500).json({
message: error.status ? error.message : 'Server Error'
})
}
}
Take note of the (await Host.find(query)) line. If the Promise that's returned from Host.find() rejects, the .map()-method won't be executed on the data and execution will jump to the catch-block.
I also heavily discourage using Babel since async/await has been natively supported in Node since version 7.6.
EDIT : The value returned is indeed a promise
I think that the error is related to your 'SendRes.then' since what SendRes returns is not a promise but it's actually is the Array allHost
Your data argument is not an argument anymore it is the value returned from the sendRes function thanks to your async / await implementation.
const hosts = sendRes();
res.status(200).json(hosts);
if you want to handle an error you must return something from your catch block so that you can can handle it here.
if (!hosts) res.status(500);
To simplify your code you can also get rid of the sendRes function and make your getAllHost express middleware an async function
async getAllHost(req, res) { /*...*/ }
try {
let allHosts = await findAllHosts();
res.status(200).json(allHosts);
} catch (error) {
res.status(500).send({ error })
}