I am attempting to make a connection to a MySQL database using Express and Mariadb. The IP for the database changes while I move the PI that it is connected to from the office to my Home. If I don't change the IP before hand then it obviously can not connect to the database, this is fine and a known issue but I want to be able to handle this error without having Node throw a fatal error and shut down
app.get("/db", (req, res) => {
console.log("ping to db!")
MariaPool.getConnection()
.then((conn) => {
conn.query("SELECT * FROM devTable")
.then((data) => {
console.log(data)
res.json(data)
})
.catch((error) => {
console.log("Poor SQL format")
})
.finally(() => {
conn.end()
})
})
.catch((error) => {
console.log("No connection")
res.json({
err: error
})
})
});
Above I attempt to make a connection to the Database, when the IP is wrong it goes to the catch block and logs No Connection as it should, however afterwards a second fatal error is thrown
text: 'Connection timeout: failed to create socket after 1002ms',
sql: null,
fatal: true,
errno: 45012,
sqlState: '08S01',
code: 'ER_CONNECTION_TIMEOUT'
this causes the Node server to shutdown. Try as I may I am unable to find a way to handle this error.
My initial thought is to somehow stop the attempts to make the socket creation once it goes into the catch block but I'm not sure if that is possible.
I've taken a look at This Question but app.use does not solve my problem
Tried the implementation of my own Promise as documented in this question and it still does not handle the fatal error
To sum it up, is it possible to handle fatal errors such as this to allow the program to continue?
Related
I'm trying to gracefully handle redis errors, in order to bypass the error and do something else instead, instead of crashing my app.
But so far, I couldn't just catch the exception thrown by ioredis, which bypasses my try/catch and terminates the current process. This current behaviour doesn't allow me to gracefully handle the error and in order to fetch the data from an alternative system (instead of redis).
import { createLogger } from '#unly/utils-simple-logger';
import Redis from 'ioredis';
import epsagon from './epsagon';
const logger = createLogger({
label: 'Redis client',
});
/**
* Creates a redis client
*
* #param url Url of the redis client, must contain the port number and be of the form "localhost:6379"
* #param password Password of the redis client
* #param maxRetriesPerRequest By default, all pending commands will be flushed with an error every 20 retry attempts.
* That makes sure commands won't wait forever when the connection is down.
* Set to null to disable this behavior, and every command will wait forever until the connection is alive again.
* #return {Redis}
*/
export const getClient = (url = process.env.REDIS_URL, password = process.env.REDIS_PASSWORD, maxRetriesPerRequest = 20) => {
const client = new Redis(`redis://${url}`, {
password,
showFriendlyErrorStack: true, // See https://github.com/luin/ioredis#error-handling
lazyConnect: true, // XXX Don't attempt to connect when initializing the client, in order to properly handle connection failure on a use-case basis
maxRetriesPerRequest,
});
client.on('connect', function () {
logger.info('Connected to redis instance');
});
client.on('ready', function () {
logger.info('Redis instance is ready (data loaded from disk)');
});
// Handles redis connection temporarily going down without app crashing
// If an error is handled here, then redis will attempt to retry the request based on maxRetriesPerRequest
client.on('error', function (e) {
logger.error(`Error connecting to redis: "${e}"`);
epsagon.setError(e);
if (e.message === 'ERR invalid password') {
logger.error(`Fatal error occurred "${e.message}". Stopping server.`);
throw e; // Fatal error, don't attempt to fix
}
});
return client;
};
I'm simulating a bad password/url in order to see how redis reacts when misconfigured. I've set lazyConnect to true in order to handle errors on the caller.
But, when I define the url as localhoste:6379 (instead of localhost:6379), I get the following error:
server 2019-08-10T19:44:00.926Z [Redis client] error: Error connecting to redis: "Error: getaddrinfo ENOTFOUND localhoste localhoste:6379"
(x 20)
server 2019-08-10T19:44:11.450Z [Read cache] error: Reached the max retries per request limit (which is 20). Refer to "maxRetriesPerRequest" option for details.
Here is my code:
// Fetch a potential query result for the given query, if it exists in the cache already
let cachedItem;
try {
cachedItem = await redisClient.get(queryString); // This emit an error on the redis client, because it fails to connect (that's intended, to test the behaviour)
} catch (e) {
logger.error(e); // It never goes there, as the error isn't "thrown", but rather "emitted" and handled by redis its own way
epsagon.setError(e);
}
// If the query is cached, return the results from the cache
if (cachedItem) {
// return item
} else {} // fetch from another endpoint (fallback backup)
My understanding is that redis errors are handled through client.emit('error', error), which is async and the callee doesn't throw an error, which doesn't allow the caller to handle errors using try/catch.
Should redis errors be handled in a very particular way? Isn't it possible to catch them as we usually do with most errors?
Also, it seems redis retries 20 times to connect (by default) before throwing a fatal exception (process is stopped). But I'd like to handle any exception and deal with it my own way.
I've tested the redis client behaviour by providing bad connection data, which makes it impossible to connect as there is no redis instance available at that url, my goal is to ultimately catch all kinds of redis errors and handle them gracefully.
Connection errors are reported as an error event on the client Redis object.
According to the "Auto-reconnect" section of the docs, ioredis will automatically try to reconnect when the connection to Redis is lost (or, presumably, unable to be established in the first place). Only after maxRetriesPerRequest attempts will the pending commands "be flushed with an error", i.e. get to the catch here:
try {
cachedItem = await redisClient.get(queryString); // This emit an error on the redis client, because it fails to connect (that's intended, to test the behaviour)
} catch (e) {
logger.error(e); // It never goes there, as the error isn't "thrown", but rather "emitted" and handled by redis its own way
epsagon.setError(e);
}
Since you stop your program on the first error:
client.on('error', function (e) {
// ...
if (e.message === 'ERR invalid password') {
logger.error(`Fatal error occurred "${e.message}". Stopping server.`);
throw e; // Fatal error, don't attempt to fix
...the retries and the subsequent "flushing with an error" never get the chance to run.
Ignore the errors in client.on('error', and you should get the error returned from await redisClient.get().
Here is what my team has done with IORedis in a TypeScript project:
let redis;
const redisConfig: Redis.RedisOptions = {
port: parseInt(process.env.REDIS_PORT, 10),
host: process.env.REDIS_HOST,
autoResubscribe: false,
lazyConnect: true,
maxRetriesPerRequest: 0, // <-- this seems to prevent retries and allow for try/catch
};
try {
redis = new Redis(redisConfig);
const infoString = await redis.info();
console.log(infoString)
} catch (err) {
console.log(chalk.red('Redis Connection Failure '.padEnd(80, 'X')));
console.log(err);
console.log(chalk.red(' Redis Connection Failure'.padStart(80, 'X')));
// do nothing
} finally {
await redis.disconnect();
}
I'm trying to make an alert window saying there's an error, When trying to post a message offline. But the catch doesn't seem to ever work, Maybe it just works in other cases?
here's my code :
return (dispatch) => {
dispatch({
type: POST_TO_DB,
});
firebase.database().ref(locationInDB).push(object)
.then((data) => {
dispatch({
type: POST_TRADE_TO_DB_SUCCESS,
}); // success
})
.catch((err) => {
console.log("failed to post...")
dispatch({
type: POST_TRADE_TO_DB_FAILED,
}); // failed
});
};
Is there an alternative? Or am I doing something wrong?
When there is no network connection, the Firebase client will keep the pending write in memory until the network connection is restored, at which point it will complete the write.
The catch() clause is triggered if the write fails on the server, not when it can't complete.
Also see:
To detect if the client is connected to the Firebase backend, see Detect if Firebase connection is lost/regained
Firebase synchronisation of locally-modified data: handling errors & global status
This problem annoys me, because I know it has something to do with me not understanding the issue properly - which makes it really hard to track down answers for, despite spending hours reading and trying different things.
My question/problem is this, I am saving a user to a mongodb database when they signup, my schema doesn't allow for duplicate emails, and sends me back an error. I am able to console log the error in the terminal, but I am having problems sending it back to the client. Or I'm having a problem doing something with it, if it comes back, I'm not too sure where in those two steps I am losing access to the error message.
Here is my POST route for saving the user:
router.post('/users', (req, res) => {
let body = _.pick(req.body, ['email', 'password']);
let user = new User(body);
user.save().then(() => { // this all works and will save the user, if there are no errors
return user.generateAuthToken();
}).then((token) => {
res.header('Authorization', `Bearer ${token}`).send(user);
}).catch((err) => { // This is where my problem is
console.log(err); // This will log the mongodb error here, about duplicate emails
res.status(500).send(err); // I'm trying to send the mongodb error message back to the client to display it on the screen (I will handle making the message friendly to read, once I can get this to work)
});
});
So my catch is getting the mongo error, and then I try to respond with it, by sending it to the client.
Here is my client side code:
axios({
method: 'post',
url: '/auth/users',
headers: {
'Content-Type': 'application/json'
},
data: {
email,
password
}
}).then((res) => {
console.log('this is the response', res);
if (res.status === 200) {
var authToken = res.headers.authorization.split(' ')[1];
authenticateUser(authToken);
this.props.history.replace('/dashboard');
} // This all works fine for a signup with no errors
}).catch((err) => {
console.log('Signup error:', err);
// I am expecting the above line of code to log the long Mongodb
// error message that I am sending back in my res.status(500).send(err)
// catch call from the server, but instead all I am getting is
// "Signup error: Error: Request failed with status code 500"
});
Either I'm not sending the error correctly, or I'm not handling it correctly when it comes back, but I have no idea which it is or why.
I can't even send back res.status(500).send('some string here') and access that string.
Thanks
Update
So I just checked in postman, by sending a POST that could cause the error, and I am getting the correct response sent through.
My server catch actually looks like this:
.catch((err) => {
res.status(500).send({message: err.message});
});
And the postman response body looks like this:
{
"message": "E11000 duplicate key error collection: authBoilerplate.users index: email_1 dup key: { : \"email#example.com\" }"
}
So I'm just not handling it correctly in my client side code, still at a loss though.
Thanks everyone, I was able to find the answer to my question, so I'm posting it here in the hope that it might help someone else.
I was definitely sending my custom error message back, I just wasn't handling it properly on the client side.
When I was using a catch call on the client and logging the error, I was expecting to see everything included in the error. It turns out that the error comes back with a response property error.response, and that is where all the messaging is.
So changing my catch call to this:
axios(//... send post in here)
.then(// ... same as in my question)
.catch((err) => {
console.log('error', err);
console.log('error response', err.response); // this is where the actual error response message is error.response.message
});
resulted in logging the stack trace and the error response:
error Error: Request failed with status code 500
at createError (eval at <anonymous> (bundle.js:541), <anonymous>:16:15)
at settle (eval at <anonymous> (bundle.js:847), <anonymous>:18:12)
at XMLHttpRequest.handleLoad (eval at <anonymous> (bundle.js:520), <anonymous>:77:7)
error response Object {data: Object, status: 500, statusText: "Internal Server Error", headers: Object, config: Object…}
I was still expecting to be able to see that I had access to that 'response' property by logging just the error, so if anyone has any insight into that, it would be great to include in the comments.
Another way of solving this is by converting the error to string.
.catch((err) => {
res.status(500).send(err.toString());
});
I'm using the following code to set up a listener for Firebase Database Ref:
export function listenToUserEventsFeed (userId, cb, errorCB) {
database.ref(`proUserEvents/${userId}`).on('value', (snapshot) => {
console.log('SNAPSHOT RECEIVED')
const feed = snapshot.val() || {}
const sortedIds = Object.keys(feed).sort((a, b) => feed[b].createdAtTimeStamp - feed[a].createdAtTimeStamp)
cb({feed, sortedIds})
}, (error) => {
console.log('SNAPSHOT ERROR: ', error)
})
}
But the console.log('SNAPSHOT ERROR: ', error) never runs if I test using no internet connection. Am I missing something or is there something wrong in my code? I essentially want to pass down the error to the errorCB() function.
The error callback will only be called in case of an error, i.e. when your current client has no permission to read the data it is trying to read.
Not having an internet connection is not an error.
If you want to detect whether there is an internet connection, listen for .info/connected.
Related:
Firebase Handling disconect to database
Android Firebase - "onDataChange" And "onCancelled" Not Being Called With No Internet Connection
How to Catch Error When Data is not Sent on Angularfire when adding data to firebase?
fields is undefined in the following code snipped, but it is not logged to the console when the error happens. In this specific instance, why, and what is the de facto way to handle this?
"Testing" is logged to the console (Line #2), but the undefined variable fields (Line #4) is not being reported. The error is returned in an API response (Line #5) but with no relevant information such as line #, stack trace, etc.
How can I make errors like this log to the console, and why are they not?
export function post(req, res) {
console.log("Testing")
User.create( getFields(req, ["name_first", "name_last"]) )
.then(user => respondJSON (res, fields, { status: 201 }))
.catch(err => respondError (res, err))
}
Since the catch is responding with an error, I get the following API response:
{
"error": true,
"data": {
"message": "fields is not defined"
}
}
I am using Babel 6 and babel-node to run my code through NPM scripts. I am using morgan logging as well. Removing the middleware for logging does not alter the error output.
The automatic logging to console is a mechanism for unhandled exceptions. Because Promises automatically catch exceptions in the callbacks, the exceptions are no-longer unhandled, so nothing will be automatically logged.
If you want it to be logged, you could perhaps add a throw err at the end of your catch block. This will convert it into an unhandled promise rejection, which is typically handled similarly to an unhandled exception.
Because you didn't actually log the error?
export function post(req, res) {
console.log("Testing")
User.create( getFields(req, ["name_first", "name_last"]) )
.then(user => respondJSON (res, fields, { status: 201 }))
.catch(err => {
console.error(err);
respondError(res, err);
});
}
I had a similar problem caused by a 'finally' which was appended to the main async function running.
run()
.finally(()=>{process.exit(0)})
modifying it to:
run()
.catch(err => {console.log(err)})
.finally(()=>{process.exit(0)})
solved the problem