How can I stop a thrown error from propagating all the way down the chain? It shows in my catch() block but it doesn't stop and crashes the server with an uncaught exception.
I am running this as part of a node cron job (node-cron) as:
var cronJob = require('cron').CronJob;
var cron = require('../lib/cron')
var c = new cronJob('* * * * * *', function() {
console.log('Cron starting');
mycode.run();
}, function() {
console.log('Cron executed');
}, true);
c.start();
In my cron.js
module.exports = {
run: function() {
return job.getAndStore().catch(function(e) {
// This prints but it keeps on going so to speak - it doesn't 'catch', just notifies me
console.log('ERROR', e);
});
}
};
Console dump:
Cron starting
ERROR [TypeError: undefined is not a function]
Cron starting
Uncaught Exception
[TypeError: undefined is not a function]
TypeError: undefined is not a function
I have to do this which I know not quite right:
try {
run();
} catch(e) {
console.log('Now it stops')
}
The run() is part of some cron library that doesn't have any promise support so I am wrapping it in the function to call it.
Edit As I think my issue is related to subsequent calls I believe it has to do with how I handle the Mongo connection on 2+ calls:
// Create a Mongo connection
Job.prototype.getDb = function(id) {
var self = this;
return new P(function(resolve, reject) {
if (!self.db) {
return Mongo.connectAsync(self.options.connection)
.then(function(c) {
self.db = c;
debug('Got new connection');
resolve(c);
});
}
debug('Got existing connection');
resolve(self.db);
});
};
// Fetch stuff
Job.prototype.getAndStore = function(c) {
return this.getDb().then(function() {
throw new Error('Boom');
});
};
Your catch callback is only executed the first time. You are getting the uncaught exception in the second run of the cron job, and it looks like your job.getAndStore() does not return a rejected promise there but throws synchronously. It shouldn't, it should always return a promise.
You can use Bluebirds Promise.try to automatically catch such exceptions and transform them into a promise rejection. Or you wrap your getAndStore function in Promise.method:
var safeGetAndStore = Promise.method(job.getAndStore.bind(job));
module.exports = {
run: function() {
return safeGetAndStore().catch(function(e) {
console.log('ERROR', e);
});
}
};
In your specific case, the problem was that your job did cache the db connection and returned that when it was already available - but you needed to return a promise with a .then method. You should simply cache the promise itself:
Job.prototype.getDb = function(id) {
if (!this.db) {
this.db = Mongo.connectAsync(self.options.connection);
return this.db;
};
Use done, at least if bluebird implements it properly it will work as you expect.
catch(..) is just alias for then(null, ..) which is promise transformer that creates another promise for further processing.
So following should work for you:
module.exports = {
run: function() {
return job.getAndStore().done(null, function(e) {
// This prints but it keeps on going so to speak - it doesn't 'catch', just notifies me
console.log('ERROR', e);
});
}
};
Related
I hope someone can give me some advice
I have created a module, that basically creates a redis connection using a singleton pattern which also uses promises.
However, before I return the redis connection, I first check if the connection is ready, on the ready event, I resolve the promise and likewise on any errors, I reject the promise.
My only concern is with this method, can I introduce memory leaks as the listeners function on ready and error may continue listening well after the promise has completed and should be cleaned up by the garbage collector.
I am not sure if this will create some kind of memory leaks..
Any advice would be much appreciated.
import redis from 'redis';
import redisearch from 'redis-redisearch';
redisearch(redis);
let redisClient: redis.RedisClient = null;
export function getRedisClient(): Promise {
return new Promise((resolve: any, reject: any) => {
if (redisClient && redisClient.connected) {
return resolve(redisClient);
}
redisClient = redis.createClient({
password: process.env.REDIS_PASSWORD,
retry_strategy: function (options) {
if (options.error && options.error.code === "ECONNREFUSED") {
// End reconnecting on a specific error and flush all commands with
// a individual error
return new Error("The server refused the connection");
}
if (options.total_retry_time > 1000 * 60 * 60) {
// End reconnecting after a specific timeout and flush all commands
// with a individual error
return new Error("Retry time exhausted");
}
if (options.attempt > 10) {
// End reconnecting with built in error
return undefined;
}
// reconnect after
return Math.min(options.attempt * 100, 3000);
},
});
redisClient.on("ready", function (error: any) {
console.log("connection is good");
return resolve(redisClient);
});
redisClient.on("error", function (error: any) {
console.log("reject error");
if (redisClient) {
redisClient.end(false);
redisClient = null;
return reject(error);
}
});
})
}
This pattern can create multiple redisClients if getRedisClient is called multiple times before any of the redisClients finish connecting. A slightly simpler pattern might be to cache a Promise<RedisClient> rather than the redisClient:
let redisClientP: Promise<RedisClient>;
function getRedisClient (): Promise<RedisClient> {
if (redisClientP) return redisClientP;
redisClientP = new Promise(...previous code)
return redisClientP;
}
If you haven't seen this type of cached Promise usage before, here's a small snippet demonstrating that you can access the .then on a Promise multiple times.
const p = new Promise((resolve) => resolve(2));
(async function main () {
for (let i = 0; i < 100; i++) {
console.log(i, await p);
}
})()
I have a custom connect function that creates a promise I want resolved once I make a websocket call and receive an acknowledgement. The remote server may be up, it may be down, but if it's unavailable I want to keep trying until I'm successful.
const socketIOClient = require('socket.io-client');
function createTimeoutCallback(callback)
{
let called = false;
let timerID = setTimeout(() => {
if (called) return;
called = true;
callback(new TimeoutError());
},
60*1000);
return function() {
if (called) return;
called = true;
clearTimeout(timerID);
callback.apply(this, arguments);
}
}
async function myConnect()
{
let mysocket = socketIOClient(url);
return new Promise((resolve, reject) => {
mysocket.emit('clientconnect', args, createTimeoutCallback((resp) => {
if (!(resp instanceof TimeoutError)) {
// SUCCESS
doSomething();
resolve();
}
// We timed out, try again
else {
mysocket.close();
setTimeout(myConnect, 60*1000);
}
}));
});
}
await connect();
// doSomething() gets called but we never get here
In the above code, if the endpoint is available, everything works fine. But I'm never returning from the myConnect() function when (1) I wait on its promise; and (2) the function needs to make several connection attempts (e.g., the server is not initially up); and (3) the endpoint finally comes back online and the connection succeeds. I suspect this has everything to do with me essentially abandoning the original promise on a reconnect attempt, but I don't want to reject it or the operation will prematurely fail.
I did find a workaround, which relies on an embedded function. With this technique there is only one promise which is always accessible and in scope, and the timer-based recursion (i.e., not stack-based) is always wrapped in that single promise that is never abandoned. I put this together before Jaromanda answered, so I can't confirm that his solution would work.
async function myConnect()
{
return new Promise((resolve, reject) => {
function innerConnect()
{
let mysocket = socketIOClient(url);
mysocket.emit('clientconnect', args, createTimeoutCallback((resp) => {
if (!(resp instanceof TimeoutError)) {
// SUCCESS
doSomething();
resolve();
}
// We timed out, try again
else {
mysocket.close();
setTimeout(innerConnect, 60*1000);
}
}));
}
innerConnect();
});
}
I can't grasp how promises work. So I figured I'd just jump in and try and create one to see if that helps. But the following returns an undefined value (arrTables):
app.get("/getTables", function (req, res) {
var arrTables = getTables().then(function(response) {
console.log("getTables() resolved");
console.log(arrTables.length);
console.log(arrTables[1].ID());
}, function(error) {
console.error("getTables() finished with an error");
});
});
function getTables() {
return new Promise(function(resolve, reject) {
while (mLobby.tlbCount() < LOBBY_SIZE) {
var objTable = new Table();
mLobby.addTable(objTable);
}
resolve(mLobby.tables);
});
}
new Table() references a custom class that makes an async database call. I'm trying to use promises to make sure that call resolves before I continue in the code. Can anyone point out where I've gone wrong?
Here's the console output:
getTables() resolved
undefined
(node:6580) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id:
1): TypeError: Cannot read property 'ID' of undefined
Edit to add: mLobby.tblCount starts out as 0, so it does enter the while loop.
The problem with the array variable. the GetTable method returns nothing and output of this method is stored in response variable not in arrTables variable. try to use response variable instead of arrTables
getTables().then(function(response) {
var arrTables = response //Added
console.log("getTables() resolved");
console.log(arrTables.length);
console.log(arrTables[1].ID);
}, function(error) {
console.error("getTables() finished with an error");
});
Adapting to the control flow of Promises can take some getting used to.
You're close! But...
var arrTables = getTables().then(function(response) {
console.log("getTables() resolved");
console.log(arrTables.length); ...
is a variable declaration.
This is analogous to writing var a = a. You cannot access arrTables in the declaration of arrTables because it hasn't been declared yet!
The anonymous function you are passing to .then() (where you erroneously try to access properties of the the, at the time, undefined variable arrTables) is the very same function that you call as resolve(mLobby.tables) within your promise.
The promise you return from getTables promises to pass mLobby.tables() to your anonymous function as response.
I recommend doing some practice with promises before trying to work them into a larger application.
The excellent nodeschool.io workshopper promise-it-wont-hurt was very helpful for me.
you can try following code.
app.get("/getTables", async function (req, res) {
var arrTables = await getTables()
console.log(arrTables.length);
console.log(arrTables[1].ID());
});
async function getTables() {
return new Promise(function(resolve, reject) {
try {
while (mLobby.tlbCount() < LOBBY_SIZE) {
var objTable = new Table();
mLobby.addTable(objTable);
}
resolve(mLobby.tables);
} catch (err) {
console.error("getTables() finished with an error");
reject(err)
}
});
}
hope it will work for you.
I have some code that I want to refactor (extract server communication methods from controller to separate service).
Example:
$http.post("/mypath", someData)
.success(function(request) {
if (request.ok) {
$scope.error = "";
_refreshAppointments();
}
else {
$scope.error = request.err;
}
})
.error(function() {
$scope.error = "Error during communicating to server";
});
My current problem is errors processing (communication with old $scope). So I want to throw the exceptions instead such lines $scope.error = "Error during communicating to server";
And catch them in controller.
Is it good idea?
If you throw an error in a vanilla environment:
setTimeout(function () {
throw new Error();
}, 1);
The error just gets lost. (window.onerror will see it though)
Even if you do:
try {
setTimeout(function () {
throw new Error();
}, 1);
} catch (e) {
console.log(e);
}
You wont see the error.
You need to wrap each asynchronous event, like:
function mySetTimeout(callback, ms) {
setTimeout(wrap_in_try_catch(callback), ms);
}
mySetTimeout(function () {
throw new Error();
});
You can now catch the error in a generic error handler, but you still can't catch it in the surrounding code.
This is where Promises come in. Not all libraries do Promises the same way (or correctly) so I don't know how good your library support is.
Roughly your code will look like:
$.ajax().then(function () {
throw new Error();
}).fail(e) {
console.log("it failed!", e);
});
If instead you have:
$.ajax().then(function () {
throw new Error();
}); // throws something like: no fail callback for rejected value error
Then your global error handler will pick it up. This ensures no error can slip through the cracks and get lost.
Getting a Promise library to work with Errors in this way is not impossible but it's a little bit tricky to set up. Once you have this though, you're good. Error handling becomes a breeze.
You'll never write a try-catch again, just a bunch of .fail() handlers.
It's definitely good idea to extract REST/http requests into model/service layer and use those services from controller. Then handling failed operation would mean rejecting a corresponding promise, in this case throwing exception in promise effectively means the same.
For example this is how your service/factory could look like:
app.factory('dataService', function($http) {
return {
load: function() {
return $http.post("/mypath", someData).then(function(response) {
if (!response.data.ok) {
return throw new Error(response.request.err);
// or return $q.reject(response.request.err);
}
return response.request;
});
}
};
});
and consuming controller would deal with promise status, resolved (success) or rejected (failed/exception):
dataService.load().then(function(request) {
$scope.error = "";
_refreshAppointments();
})
.catch(function(err) {
$scope.error = err || "Error during communicating to server";
});
I am wondering how to chain errors on Q using finally.
Consider following code
function p1() {
throw new Error("p1 error");
}
function p2() {
throw new Error("p2 error");
}
function p3() {
return Q.fcall(p1)
.finally(function () {
return Q.fcall(p2);
});
}
p3()
.done();
The Error with message "p1 error" is lost since it was override by Error "p2 error". How can I throw all the errors (or combine the errors)?
Currently, I am working on a socket connection on nodejs. I am using .finally() to close the socket after each connection.
However, errors (eg: authentication error) before .finally() will be overriden by one in .finally() (eg: connection close error). So, I am wondering how to get all errors
Thanks
You could do something like this:
function p3() {
return Q.fcall(p1)
.finally(function(p) {
return Q.fcall(p2).catch(function(e) {
if (!p.isRejected()) throw e;
var agg = new Error("multiple errors occured");
agg[0] = p.inspect().reason;
agg[1] = e;
agg.length = 2;
throw agg;
});
});
}
(based on Bluebird's AggregateError)
Of course it is tedious, but I can't imagine something better.