I am using nodejs for websocket connection(https://github.com/websockets/ws). I defines a connect method to return Promise to caller. This is the code:
connect = () => {
return new Promise((resolve, reject) => {
try {
this.ws = new WebSocket(this.url, {});
this.ws.on("open", () => {
console.log("connected:", this.ws.readyState);
resolve();
});
this.ws.on("close", function close() {
console.log("disconnected");
// how to throw error exception here?
});
} catch (err) {
reject(err);
}
});
};
the caller will call this method and try to reconnect if the connection failed.
connect().then(() => {
...
}).catch(() => {
// do reconnect
})
it works fine if it fails to connect on the first time. But what if the connection drops. How can I let caller knows that there is an error happens?
One solution I can think of is to create an event emitter to notify caller that we need to reconnect. But if the code is used in many places in my project, I have to pass the event emitter to many places. Is there any better way to do that?
It's unclear for me which npm package you're using but if it's ws you can refer to this part of its README and adapt to your use case if necessary:
https://github.com/websockets/ws/blob/master/README.md#how-to-detect-and-close-broken-connections
Related
I've been having trouble with handling data from MongoDB using MongoJS where I have a promise that refuses to resolve. I am honestly confused why this does not work. It works completely fine on my local machine but once I move it to my server it breaks. Except it only breaks as a module. Is it because I'm using deasync? If it is, how do I fix it?
Here's what I'm doing compressed down to what doesn't work for me
In the index:
const db = require('./db')
//Obviously this works I'm just showing what I mean by "module"
data = db.get('somedata') //Passthrough for getData
console.log(data)
//On local machine, console says: thedata
//On deb server, console says: Promise { 'thedata' }
//On deb server, console ALSO says: MongoDB request timed out: Promise { 'thedata' }
// ^ (this is from the timeout part I added because it froze)
In the DB module:
const db = mongojs(`connectionURIstring`, ['bot'])
const bdata = db.bot
function syncP(promise) {
var answer;
promise.then(value => {
//Runs every time on local machine but not on server as module
answer = value
})
//I also have .catch but it's not relevant
setTimeout(function () {
//Never runs on local machine but runs every time on server as module
if (answer) return;
answer = promise
console.error('MongoDB request timed out:', promise)
}, 2000)
require('deasync').loopWhile(function () { return !answer })
return answer
}
function getData(datapath) {
//Datapath processing here
var GDP = new Promise((resolve, reject) => {
bdata.findOne({
_id: primary
}, function (err, dataJ) {
//Stuff where I format data, comes out as variable data
resolve(data)
})
})
return syncP(GDP)
}
I genuinely don't know why this doesn't work. There's no reason it shouldn't work as a module if it works independently. It works on another computer. The only thing I can assume is that it's something to do with how deasync works, but even then, there's no reason why that would interfere with the promise, as it's separate from the promise itself.
This is driving me insane.
I want to test the saveRecords function for the failure and before that, I have to authenticate and connect the MongoDB. This is the code.
before(() => {
sinon.stub(Authentication, 'authenticate').returns(true);
sinon.stub(mongodb, 'connect').resolves("connected");
sinon.stub(models, 'saveRecords').throws(new Error("Error while saving record"));
});
it('Should error out if record is not inserted into the mongodb
collection', () => {
orderWebhook(req, res)
expect(res.result).to.contain("Error while saving record");
});
Here is the code I am testing.
exports.orderWebhook = async (req, res) => {
try {
const isAuthenticated = Authentication.authenticate(req);
if (isAuthenticated) {
await mongodb.connect();
await models.saveRecords(req.body");
res.status(200).send('Saved Successfully!');
} else {
res.status(403).send('Error! Auth failed!');
}
} catch (error) {
res.status(400).send(error.message);
}
}
I am assuming that this code will stub the authenticate then connect MongoDB and then try to insert the record and throw the error. But it is running two times when I debug with the VSCode debugger.
The first time it is returning true for the authenticate function and not resolving the MongoDB connect and return to expect immediately.
The second time it is running all three properly and throwing the expected error.
It is failing when I run the test in the terminal, What could be the issue?
Update: I noticed that the problem is related to the promise. Sinon is resolving the request and I am using await mongodb.connect(); but it is not working as expected, and if I remove await and return value instead of promise then it works.
In a NodeJS v10.x.x environment, when trying to create a PDF page from some HTML code, I'm getting a closed page issue every time I try to do something with it (setCacheEnabled, setRequestInterception, etc...):
async (page, data) => {
try {
const {options, urlOrHtml} = data;
const finalOptions = { ...config.puppeteerOptions, ...options };
// Set caching flag (if provided)
const cache = finalOptions.cache;
if (cache != undefined) {
delete finalOptions.cache;
await page.setCacheEnabled(cache); //THIS LINE IS CAUSING THE PAGE TO BE CLOSED
}
// Setup timeout option (if provided)
let requestOptions = {};
const timeout = finalOptions.timeout;
if (timeout != undefined) {
delete finalOptions.timeout;
requestOptions.timeout = timeout;
}
requestOptions.waitUntil = 'networkidle0';
if (urlOrHtml.match(/^http/i)) {
await page.setRequestInterception(true); //THIS LINE IS CAUSING ERROR DUE TO THE PAGE BEING ALREADY CLOSED
page.once('request', request => {
if(finalOptions.method === "POST" && finalOptions.payload !== undefined) {
request.continue({method: 'POST', postData: JSON.stringify(finalOptions.payload)});
}
});
// Request is for a URL, so request it
await page.goto(urlOrHtml, requestOptions);
}
return await page.pdf(finalOptions);
} catch (err) {
logger.info(err);
}
};
I read somewhere that this issue could be caused due to some await missing, but that doesn't look like my case.
I'm not using directly puppeteer, but this library that creates a cluster on top of it and handles processes:
https://github.com/thomasdondorf/puppeteer-cluster
You already gave the solution, but as this is a common problem with the library (I'm the author 🙂) I would like to provide some more insights.
How the task function works
When a job is queued and ready to be executed, puppeteer-cluster will create a page and call the task function (given to cluster.task) with the created page object and the queued data. The cluster then waits until the Promise is finished (fulfilled or rejected) and will close the page and execute the next job in the queue.
As an async-function is implicitly creating a Promise, this means as soon as the async-function given to the cluster.task function is finished, the page is closed. There is no magic happening to determine if the page might be used in the future.
Waiting for asynchronous events
Below is a code sample with a common mistake. The user might want to wait for an external event before closing the page as in the (not working) example below:
Non-working (!) code sample:
await cluster.task(async ({ page, data }) => {
await page.goto('...');
setTimeout(() => { // user is waiting for an asynchronous event
await page.evaluate(/* ... */); // Will throw an error as the page is already closed
}, 1000);
});
In this code, the page is already closed before the asynchronous function is executed. To correct way to do this would be to return a Promise instead.
Working code sample:
await cluster.task(async ({ page, data }) => {
await page.goto('...');
// will wait until the Promise resolves
await new Promise(resolve => {
setTimeout(() => { // user is waiting for an asynchronous event
try {
await page.evalute(/* ... */);
resolve();
} catch (err) {
// handle error
}
}, 1000);
});
});
In this code sample, the task function waits until the inner promise is resolved until it resolves the function. This will keep the page open until the asynchronous function calls resolve. In addition, the code uses a try..catch block as the library is not able to catch events thrown inside asynchronous code blocks.
I got it.
I was indeed forgetting an await to the call that was made to the function I posted.
That call was in another file that I use fot the cluster instance creation:
async function createCluster() {
//We will protect our app with a Cluster that handles all the processes running in our headless browser
const cluster = await Cluster.launch({
concurrency: Cluster[config.cluster.concurrencyModel],
maxConcurrency: config.cluster.maxConcurrency
});
// Event handler to be called in case of problems
cluster.on('taskerror', (err, data) => {
console.log(`Error on cluster task... ${data}: ${err.message}`);
});
// Incoming task for the cluster to handle
await cluster.task(async ({ page, data }) => {
main.postController(page, data); // <-- I WAS MISSING A return await HERE
});
return cluster;
}
I am writing a simple port scanner using core net module from Node.js. I am getting a 'Callback was already called' error with my code. Can you spot please where the error is coming from? Below is my code:
const net = require('net')
const async = require('async')
function findPortStatus(host, port, timeout, cb) {
const socket = new net.Socket()
socket.setTimeout(timeout, () => {
// couldn't establish a connection because of timeout
socket.destroy()
return cb(null, null)
})
socket.connect(port, host, () => {
// connection established
return cb(null, port)
})
socket.on('error', (err) => {
// couldn't establish a connection
return cb(null, null)
})
}
const funcs = []
for (let port = 0; port <= 80; port++) {
funcs.push(function(callback) {
findPortStatus('192.30.253.112', port, 4000, (err, port) => {
if (!err) {
return callback(null, port)
}
})
})
}
async.parallel(funcs, (err, ports) => {
if (err) {
console.error(err.message)
} else {
for (let port of ports) {
if (port) {
console.log(port)
}
}
}
})
Not sure if this is related, but you really should pass something to the callback when you call it. null,null isn't very useful for debugging. What I would suggest is timeout events in your context are probably not errors, but they are informative. You could just cb(null, 'timeout') or cb(null, {state: 'timedOut', port: port}) or something to better keep track of what worked and what didn't.
The most likely candidate for your actual error, though, is if your socket emits an error or timeout event after the connect event was already successful. Dropped connection or the like. If all you're looking for is a 'ping'-like functionality (across more than just ICMP obviously), then you should probably close the connection as soon as you get a connect and/or remove the other event listeners as part of the connect listener's handler.
Finally, the node docs suggest you not call socket.connect() directly, unless implementing a custom socket (which it doesn't seem like you are), but to use net.createConnection() instead; not sure that'll help you but it's worth noting.
It looks like the successfully connected sockets are subsequently timing out (which makes sense, as you connect but then do nothing with the connection, so it times out).
If you disconnect from a socket once you have recorded a successful connection, then that should clear up the error.
I want my application (lets say a simple node file for now) to work as it is even if redis is not available. I'm not able to do it the correct way. This is what I've tried.
var redis = require('redis');
var redisClient = null;
var getRedisClient = function(){
if(redisClient){
return redisClient;
}
try {
redisClient = redis.createClient({connect_timeout : 5000, max_attempts : 1});
redisClient.on("error", function(err) {
console.error("Error connecting to redis", err);
redisClient = null;
});
return redisClient;
} catch(ex){
console.log("error initialising redis client " + ex);
return null;
}
};
try {
var client = getRedisClient();
console.log("done!");
} catch (ex){
console.log("Exception");
}
However, with this code my application exits if redis is not available (it shouldn't because i've not given a process.exit() command).
How can I solve this?
Checking for Successful Connection on Start
Using a promise, you could guarantee that at least initially, you were able to connect to redis without error within a specified time period:
const redis = require('redis');
const Promise = require('bluebird');
function getRedisClient(timeoutMs){
return new Promise((resolve, reject) => {
const redisClient = redis.createClient();
const timer = setTimeout(() => reject('timeout'), timeoutMs);
redisClient.on("ready", () => {
clearTimeout(timer);
resolve(redisClient);
});
redisClient.on("error", (err) => {
clearTimeout(timer);
reject(err);
});
});
};
const redisReadyTimeoutMs = 10000;
getRedisClient(redisReadyTimeoutMs)
.then(redisClient => {
// the client has connected to redis sucessfully
return doSomethingUseful();
}, error => {
console.log("Unable to connect to redis", error);
});
You Need Proper Error Handling
The redis client being non-null does NOT guarantee using it won't throw an error.
you could experience infrastructure misfortune e.g. crashed redis process, out of memory or network being down.
a bug in your code could cause an error e.g. invalid or missing arguments to a redis command.
You should be handling redis client errors as a matter of course.
DON'T null the Redis Client on Error
It won't give you much but it will force you to check for null every time you try and use it.
The redis client also has inbuilt reconnect and retry mechanisms that you'll miss out on if you null it after the first error. See the redis package docs, look for retry_strategy.
DO Wrap your redis client code with try .. catch ... or use .catch in your promise chain.
DO Make use of a retry_strategy.