I've recently come across a problem when working with the built-in req.accepts, req.acceptsLanguages, req.acceptsCharsets, and req.acceptsEncodings functions in express.
I have an express middleware function like this:
function acceptCheckpoint(acceptOpts) {
// Calling the following function results in a TypeError.
function checkAccept(req, res, opts) {
let acceptFunction = null;
switch (opts.whichAccept) {
case "type":
acceptFunction = req.accepts;
break;
case "lang":
acceptFunction = req.acceptsLanguages;
break;
case "charset":
acceptFunction = req.acceptsCharsets;
break;
case "encoding":
acceptFunction = req.acceptsEncodings;
break;
default:
acceptFunction = req.accepts;
break;
}
return acceptFunction(opts.acceptedTypes);
}
return (req, res, next) => {
const accepted = [];
Object.getOwnPropertyNames(acceptOpts).forEach(key => {
if (key === "ignoreAcceptMismatch") { return; }
const acceptsType = checkAccept(req, res, {
whichAccept: key,
acceptedTypes: acceptOpts[key]
});
accepted.push(acceptsType);
});
if (accepted.some(type => !type) && !acceptOpts.ignoreAcceptMismatch) {
res.type("html");
res.status(406);
res.send("<h1>406 Not Acceptable.</h1>");
return;
}
next();
};
}
Which, in theory, should work. But the program keeps complaining and logs this error:
TypeError: Cannot read property 'headers' of undefined
at new Accepts (/Users/hortoncheng/Desktop/Programs/colonialwars/dev/node_modules/accepts/index.js:37:22)
at Accepts (/Users/hortoncheng/Desktop/Programs/colonialwars/dev/node_modules/accepts/index.js:34:12)
at req.accepts (/Users/hortoncheng/Desktop/Programs/colonialwars/dev/node_modules/express/lib/request.js:133:16)
at checkAccept (/Users/hortoncheng/Desktop/Programs/colonialwars/dev/Lib/middleware.js:208:12)
at /Users/hortoncheng/Desktop/Programs/colonialwars/dev/Lib/middleware.js:216:27
at Array.forEach (<anonymous>)
at /Users/hortoncheng/Desktop/Programs/colonialwars/dev/Lib/middleware.js:214:44
at Layer.handle [as handle_request] (/Users/hortoncheng/Desktop/Programs/colonialwars/dev/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/Users/hortoncheng/Desktop/Programs/colonialwars/dev/node_modules/express/lib/router/index.js:317:13)
at /Users/hortoncheng/Desktop/Programs/colonialwars/dev/node_modules/express/lib/router/index.js:284:7
The thing is, when I use req.accepts or one of those .accepts functions in the main function (acceptCheckpoint), like this:
// Pretend we're in acceptCheckpoint...
// This works.
accepted.push(req.accepts("html"));
It works. And, when I log the req object in either of those functions, it returns the expected value. I've also tried logging the req object in the request.js file of the express module, and there, it returned undefined. So that led me to believe that it was a problem with express itself. I tried deleting package-lock.json and node_modules, and then running npm install. Didn't fix it. And yes, I'm calling the express middleware function correctly. Any idea why this is happening?
I'm using express v4.17.1, Node.JS v12.18.1, and NPM v6.14.5.
The function is presumably trying to get req from its this context. But you're not passing functions with context.
Change this line:
return acceptFunction(opts.acceptedTypes);
to:
return acceptFunction.call(req, opts.acceptedTypes);
The first argument to the call() method is the object that should be used as this in the called function.
Related
I am getting the dreaded Cannot access 'server' before initialization error in code that is identical to code that's running in production.
The only things that have changed are my OS version (macOS 10.11->10.14) my NodeJS version (10->12) and my VSCode launch.json, but I cannot see anything in either that would cause an issue. My Node version went from 10 to 12, but in production it went from 8 to 15 without issue. I routinely keep launch.json pretty sparse, and the same error happens using node server in Terminal.
Here is the offending code. The issue occurs because I have shutdown() defined before server and it references server. It's written to add an event-handler and then cause the event. Yes, it could be refactored but it already works. It works, really. In 21 instances spread over 7 servers.
I have tried changing the declaraion/init of server from const to var but that does not fix it. As mentioned, this is code that's running in prod! What's wrong with my environment?
Maybe a better question is: why did this ever work?
'use strict'
const fs = require('fs');
const https = require('https');
const cyp = require('crypto').constants;
const stoppable = require('./common/stoppable.js');
const hu = require('./common/hostutil');
process.on('uncaughtException', err => {
wslog.error(`Uncaught Exception: ${err} ${err.stack}`);
shutdown();
});
process.on('unhandledRejection', (reason, p) => {
wslog.error(`Unhandled Promise Rejection: ${reason} - ${p}`);
});
// 'shutdown' is a known static string sent from node-windows wrapper.js if the service is stopped
process.on('message', m => {
if (m == 'shutdown') {
wslog.info(`${wsconfig.appName} has received shutdown message`);
shutdown();
}
});
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGHUP', shutdown);
function shutdown() {
httpStatus = 503; // Unavailable
wslog.info(`${wsconfig.appName} httpStatus now ${httpStatus} - stopping server...`);
// Error happens on this next line; It should not execute till after server is running already
server.on('close', function () {
wslog.info(`${wsconfig.appName} HTTP server has stopped, now exiting process.`);
process.exit(0)
});
server.stop();
}
// Init and start the web server/listener
var combiCertFile = fs.readFileSync(wsconfig.keyFile, 'utf8');
var certAuthorityFile = fs.readFileSync(wsconfig.caFile, 'utf8');
var serverOptions = {
key: combiCertFile,
cert: combiCertFile,
ca: certAuthorityFile,
passphrase: wsconfig.certPass,
secureOptions: cyp.SSL_OP_NO_TLSv1 | cyp.SSL_OP_NO_TLSv1_1
};
var server = https.createServer(serverOptions, global.app)
.listen(wsconfig.port, function () {
wslog.info(`listening on port ${wsconfig.port}.`);
});
server.on('clientError', (err, socket) => {
if (err.code === 'ECONNRESET' || !socket.writable) { return; }
// ECONNRESET was already logged in socket.on.error. Here, we log others.
wslog.warn(`Client error: ${err} ${err.stack}`);
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.on('error', (err)=>{
if ( err.code === 'EADDRINUSE' ) {
wslog.error(`${err.code} FATAL - Another ${wsconfig.appName} or app is using my port! ${wsconfig.port}`);
} else {
wslog.error(`${err.code} FATAL - Server error: ${err.stack}`);
}
shutdown();
})
combiCertFile = null;
certAuthorityFile = null;
// Post-instantiation configuration required (may differ between apps: need an indirect way to plug in app-specific behavior)
stoppable(server, wsconfig.stopTimeout);
// Load all RESTful endpoints
const routes = require('./routes/');
This is a runtime error, which happens only in a very specific situation. But actually this exact error shouldn't happen with var server = ... but only with const server = ... or let server = .... With var server = ... the error message should say "Cannot read properties of undefined"
What happens
You have an error handler for uncaughtException which is calling shutdown() and in shutdown() you are referencing your server. But consider what happens if your code throws an exception before you initialized your server. For instance if your cert or key cannot be read from the disk, cert or key are invalid ... So nothing will be assigned to server, and an exception will be raised.
Then the handler for your uncaught exception will fire and call the shutdown() function, which then tries to access the server, which of course hasn't been initialized yet.
How to fix
Check what the unhandled exception is, that is thrown before your server is initialized and fix it. In your production environment, there is probably no exception, because the configuration and environment is properly set up. But there is at least one issue in your develepment environment, which causes an exception.
Difference between var and const
And the difference between var server = ... and const server = ... is quite a subtle one. For both, the declaration of the variable is hoisted up to the top of their respective scope. In your case it's always global, also for const. But variables declared as var are assigned a value of undefined whereas variables declared as let/const are not initialized at all.
You can easily reproduce this error if you uncomment either error1 or error2 in the following code. But error3 alone won't produce this ReferenceError because bar will already be initialized. You can also replace const bar = with var bar = and you will see, that you get a different error message.
process.on("uncaughtException", err => {
console.log("uncaught exception");
console.log(err);
foo();
});
function foo() {
console.log("foo");
console.log(bar.name);
}
function init() {
// throw new Error("error1");
return { name: "foobar"}
}
// throw new Error("error2");
const bar = init();
//throw new Error("error3");
I've come across a very peculiar bug that I can't seem to solve. In Node.js, I have an API wrapper library called Markitable:
var request = require('request');
var Q = require('q');
/**
* Markit on Demand API Client
* #constructor
* #author Chandler Freeman <chandler.freeman#gmail.com>
*/
var Markitable = function() {
};
module.exports = Markitable;
endpoints = {
lookup: 'http://dev.markitondemand.com/Api/v2/Lookup',
quote: 'http://dev.markitondemand.com/Api/v2/Quote',
interactivechart: 'http://dev.markitondemand.com/Api/v2/InteractiveChart'
};
/**
* Company lookup
* #httpMethod GET
* #param {ticker} String A string containing the ticker for the stock
* #param {callback} callback (Optional) If callback is passed, than the function will use callbacks
* #promises yes By default, this function uses promises
*/
Markitable.prototype.lookup = function(ticker, callback) {
var options = {
url: endpoints.lookup,
headers: this.headers,
qs: { 'input': ticker.toUpperCase() },
method: 'GET',
json: true
};
if (!callback) {
var d = Q.defer();
request(options, function(err, httpResponse, body) {
if (err) return d.reject(err);
d.resolve(body);
});
return d.promise;
}
else
request(options, callback);
};
As you can see, all it does is fetch data from the Markit on Demand API and return a Q promise. However, every time I include the
var Markitable = require('markitable');
statement anywhere in my code, when I navigate to the / route of my application I receive
Failure on / route: TypeError: Cannot read property 'user' of undefined
I am using Express I have absolutely no idea at all why this is happening; I've reread the source several times, checked source control changes, everything, yet I can't find the root of the issue. This behavior only persists when the library is included; as soon as I remove that statement, everything works perfectly. I don't understand because the code for this library was the exact same code I used from another library I wrote, and the first one looks great. Here is the code for my routes file:
var User = require('../app/models/user');
var Robinhood = require('marian');
var util = require('util');
var Q = require('q');
module.exports = function(app, passport) {
// =========================================
// Table of Contents(Search by name)
//
// 1. Root
//
// =========================================
// ********************* 1. Root *********************
app.get('/', isLoggedIn, asyncCallback(function*(req, res) {
rh = new Robinhood(req.user.local.rhtoken);
var viewData = { user : req.user };
try {
// e: <Cannot read property user of undefined>
viewData.rhUser = yield rh.getUser();
viewData.rhUser.basic_info = yield rh.getBasicInfo();
viewData.rhPortfolio = yield rh.getPortfolio();
viewData.getOrders = yield rh.getOrders();
viewData.rhAccount = yield rh.getAccount();
viewData.active = { page : 'dashboard' };
res.render('main_pages/maindashboard.ejs', viewData);
}
catch(e) {
console.log("Failure on / route: " + e);
res.render('meta/500.ejs');
}
}));
};
function isLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/login');
}
// Code originally from James Long
// http://jlongster.com/A-Study-on-Solving-Callbacks-with-JavaScript-Generators
function asyncCallback(gen) {
return function() {
return Q.async(gen).apply(null, arguments).done();
};
}
Any ideas about why this strange behavior may be occurring? I can't imagine why importing a library would affect the 'req' object, but somehow it does. Could this be a bug in Express?
EDIT:
Forgot the stack trace:
TypeError: Cannot read property 'user' of undefined
at Robinhood.getUser (/vagrant/StockFire/node_modules/marian/index.js:110:26)
at /vagrant/StockFire/app/routes.js:28:40
at next (native)
at Function.continuer (/vagrant/StockFire/node_modules/q/q.js:1278:45)
at /vagrant/StockFire/node_modules/q/q.js:1305:16
at /vagrant/StockFire/app/routes.js:226:29
at Layer.handle [as handle_request] (/vagrant/StockFire/node_modules/express/lib/router/layer.js:95:5)
at next (/vagrant/StockFire/node_modules/express/lib/router/route.js:131:13)
at isLoggedIn (/vagrant/StockFire/app/routes.js:217:16)
at Layer.handle [as handle_request] (/vagrant/StockFire/node_modules/express/lib/router/layer.js:95:5)
at next (/vagrant/StockFire/node_modules/express/lib/router/route.js:131:13)
at Route.dispatch (/vagrant/StockFire/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/vagrant/StockFire/node_modules/express/lib/router/layer.js:95:5)
at /vagrant/StockFire/node_modules/express/lib/router/index.js:277:22
at Function.process_params (/vagrant/StockFire/node_modules/express/lib/router/index.js:330:12)
at next (/vagrant/StockFire/node_modules/express/lib/router/index.js:271:10)
Solved the issue. Simple bug, but very hard to find. I discovered that a variable named endpoints existed in both libraries, but neither was declared with var, meaning that the variable was overwritten since they both existed in the javascript global scope. Lessons learned: Always check variable scope.
I use the following code
require('./ut/valid').validateFile()
});
in the validate file when I found some duplicate in config I send error
like following
module.exports = {
validateFile: function (req) {
...
if(dup){
console.log("Duplicate found: ");
return new Error("Duplicate found: ");
}
dup is true and the error should be thrown, how should I "catch" it in async method ?
I tried also like following
require('./ut/valid').validateFile(function() {
process.exit(1);
});
what I miss here, I was able to see the console log...
Your approach doens't work because you're doing something asynchronous. A common solution is to use callbacks. In node.js it's common to use a pattern called error first callbacks.
This means you need to pass a callback function to your file validation method and either return an error or your file:
// './utils/validate.js'
module.exports = {
/**
* Validates a file.
*
* #param {Function} next - callback function that either exposes an error or the file in question
*/
file: function (next) {
// ...
if (duplicate) {
console.log('Duplicate found!');
var error = new Error('Duplicate File');
// Perhaps enrich the error Object.
return next(error);
}
// Eveything is ok, return the file.
next(null, file);
}
};
The you can use it like this:
// './app.js'
var validate = require('./utils/validate');
validate.file(function (err, file) {
if (err) {
// Handle error.
process.exit(1);
}
// Everything is ok, use the file.
console.log('file: ', file);
});
I don't have much knowledge in node, but I can tell you, you are returning an error object, which is not an error from JS perspective, it's just an object, to get an error you have to throw an error like:
throw true;
or
throw new Error("Duplicate found: ");
that way it is handled as an error not as a return value
I am using request package for node.js
Code :
var formData = ({first_name:firstname,last_name:lastname,user_name:username, email:email,password:password});
request.post({url:'http://localhost:8081/register', JSON: formData}, function(err, connection, body) {
exports.Register = function(req, res) {
res.header("Access-Control-Allow-Origin", "*");
console.log("Request data " +JSON.stringify(req));
Here I am getting this error :
TypeError: Converting circular structure to JSON
Can anybody tell me what is the problem
JSON doesn't accept circular objects - objects which reference themselves. JSON.stringify() will throw an error if it comes across one of these.
The request (req) object is circular by nature - Node does that.
In this case, because you just need to log it to the console, you can use the console's native stringifying and avoid using JSON:
console.log("Request data:");
console.log(req);
I also ran into this issue. It was because I forgot to await for a promise.
Try using this npm package. This helped me decoding the res structure from my node while using passport-azure-ad for integrating login using Microsoft account
https://www.npmjs.com/package/circular-json
You can stringify your circular structure by doing:
const str = CircularJSON.stringify(obj);
then you can convert it onto JSON using JSON parser
JSON.parse(str)
I was able to get the values using this method, found at careerkarma.com
Output looks like this.
I just run this code in the debugger console. Pass your object to this function.
Copy paste the function also.
const replacerFunc = () => {
const visited = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (visited.has(value)) {
return;
}
visited.add(value);
}
return value;
};
};
JSON.stringify(circObj, replacerFunc());
I forgotten to use await keyword in async function.
with the given systax
blogRouter.put('/:id', async (request, response) => {
const updatedBlog = Blog.findByIdAndUpdate(
request.params.id,
request.body,
{ new: true }
);
response.status(201).json(updatedBlog);
});
Blog.findByIdAndUpdate should be used with the await keyword.
use this https://www.npmjs.com/package/json-stringify-safe
var stringify = require('json-stringify-safe');
var circularObj = {};
circularObj.circularRef = circularObj;
circularObj.list = [ circularObj, circularObj ];
console.log(stringify(circularObj, null, 2));
stringify(obj, serializer, indent, decycler)
It's because you don't an async response For example:
app.get(`${api}/users`, async (req, res) => {
const users = await User.find()
res.send(users);
})
This is because JavaScript structures that include circular references can't be serialized with a"plain" JSON.stringify.
https://www.npmjs.com/package/circular-json mentioned by #Dinesh is a good solution. But this npm package has been deprecated.
So use https://www.npmjs.com/package/flatted npm package directly from the creator of CircularJSON.
Simple usage. In your case, code as follows
import package
// ESM
import {parse, stringify} from 'flatted';
// CJS
const {parse, stringify} = require('flatted');
and
console.log("Request data " + stringify(req));
If you are sending reponse , Just use await before response
await res.json({data: req.data});
Came across this issue in my Node Api call when I missed to use await keyword in a async method in front of call returning Promise. I solved it by adding await keyword.
I was also getting the same error, in my case it was just because of not using await with Users.findById() which returns promise, so response.status().send()/response.send() was getting called before promise is settled (fulfilled or rejected)
Code Snippet
app.get(`${ROUTES.USERS}/:id`, async (request, response) => {
const _id = request.params.id;
try {
// was getting error when not used await
const user = await User.findById(_id);
if (!user) {
response.status(HTTP_STATUS_CODES.NOT_FOUND).send('no user found');
} else {
response.send(user);
}
} catch (e) {
response
.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR)
.send('Something went wrong, try again after some time.');
}
});
For mongodb
so if you are getting errors while fetching data from MongoDB then the problem is async
previously
app.get('/users',(req,res,next)=>{
const user=chatUser.find({});
if(!user){
res.status(404).send({message:"there are no users"});
}
if(user){
res.json(user);
}
})
After
app.get('/users',async(req,res,next)=>{
const user=await chatUser.find({});
if(!user){
res.status(404).send({message:"there are no users"});
}
if(user){
res.json(user);
}
})
I came across this issue when not using async/await on a asynchronous function (api call). Hence adding them / using the promise handlers properly cleared the error.
This error message "TypeError: Converting circular structure to JSON" typically occurs when you try to stringify an object that contains circular references using JSON.stringify().
A circular reference occurs when an object references itself in some way. For example, consider the following code:
const obj = { foo: {} };
obj.foo.obj = obj;
In this example, obj contains a circular reference because the foo property of obj contains a reference to obj itself.
When you try to stringify an object like this using JSON.stringify(), it will fail with the error message "TypeError: Converting circular structure to JSON".
To solve this issue, you can use a third-party library like flatted or circular-json, which are specifically designed to handle circular references in JavaScript objects. Here's an example using flatted:
const flatted = require('flatted');
const obj = { foo: {} };
obj.foo.obj = obj;
const str = flatted.stringify(obj);
console.log(str);
In this example, we use flatted.stringify() instead of JSON.stringify(), and it successfully converts the object to a string without throwing an error.
Alternatively, you can modify your object to remove the circular reference before trying to stringify it. For example:
const obj = { foo: {} };
obj.foo.bar = 'baz';
// add circular reference
obj.foo.obj = obj;
// remove circular reference
obj.foo.obj = undefined;
const str = JSON.stringify(obj);
console.log(str);
In this example, we add the circular reference and then remove it before trying to stringify the object. This approach works well if you don't need to preserve the circular reference in the stringified object.
I had a similar issue:-
const SampleFunction = async (resp,action) => {
try{
if(resp?.length > 0) {
let tempPolicy = JSON.parse(JSON.stringify(resp[0]));
do something
}
}catch(error){
console.error("consoleLogs.Utilities.XXX.YYY", error);
throw error;
}
.
.
I put await before JSON.parse(JSON.stringify(resp[0])).
This was required in my case as otherwise object was read only.
Both Object.create(resp[0]) and {...resp[0]} didn't suffice my need.
If an object has a different type of property like mentioned in the above image, JSON.stringify() will through an error.
Try this as well
console.log(JSON.parse(JSON.stringify(req.body)));
TypeError: Converting circular structure to JSON in nodejs:
This error can be seen on Arangodb when using it with Node.js, because storage is missing in your database. If the archive is created under your database, check in the Aurangobi web interface.
I have app in Node.js and Express. I need to write tests for it. I have a problem with handling Express app errors. I found this How do I catch node.js/express server errors like EADDRINUSE?, but it doesn't work for me, I don't know why. I want to handle errors, which can occured while expressApp.listen() is executing (EADDRINUSE, EACCES etc.).
express = require('express')
listener = express()
#doesn't work for me
listener.on('uncaughtException', (err) ->
#do something
)
#doesn't work too
listener.on("error", (err) ->
#do something
)
#this works, but it caughts all errors in process, I want only in listener
process.on('uncaughtException', (err) ->
#do something
)
listener.listen(80) #for example 80 to get error
Any ideas?
This should do the trick:
listener.listen(80).on('error', function(err) { });
What listener.listen actually does is create a HTTP server and call listen on it:
app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
First off, expressJS does not throw the uncaughtException event, process does, so it's no surprise your code doesn't work.
So use: process.on('uncaughtException',handler) instead.
Next, expressJS already provides a standard means of error handling which is to use the middleware function it provides for this purpose, as in:
app.configure(function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
This function returns an error message to the client, with optional stacktrace, and is documented at connectJS errorHandler.
(Note that errorHandler is actually part of connectJS and is only exposed by expressJS.)
If the behavior the existing errorHandler provides is not sufficient for your needs, its source is located at connectJS's errorHandler middleware and can be easily modified to suit your needs.
Of course, rather than modifying this function directly, the "correct" way to do this is to create your own errorHandler, using the connectJS version as a starting point, as in:
var myErrorHandler = function(err, req, res, next){
...
// note, using the typical middleware pattern, we'd call next() here, but
// since this handler is a "provider", i.e. it terminates the request, we
// do not.
};
And install it into expressJS as:
app.configure(function(){
app.use(myErrorHandler);
});
See Just Connect it, Already for an explanation of connectJS's idea of filter and provider middleware and How To Write Middleware for Connect/Express for a well-written tutorial.
You might also find these useful:
How to handle code exceptions in node.js?
Recover from Uncaught Exception in Node.JS
Finally, an excellent source of information regarding testing expressJS can be found in its own tests.
Mention: Marius Tibeica answer is complete and great, also david_p comment is. As too is Rob Raisch answer (interesting to explore).
https://stackoverflow.com/a/27040451/7668448
https://stackoverflow.com/a/13326769/7668448
NOTICE
This first method is a bad one! I leave it as a reference! See the Update section! For good versions! And also for the explanation for why!
Bad version
For those who will find this useful, here a function to implement busy port handling
(if the port is busy, it will try with the next port, until it find a no busy port)
app.portNumber = 4000;
function listen(port) {
app.portNumber = port;
app.listen(port, () => {
console.log("server is running on port :" + app.portNumber);
}).on('error', function (err) {
if(err.errno === 'EADDRINUSE') {
console.log(`----- Port ${port} is busy, trying with port ${port + 1} -----`);
listen(port + 1)
} else {
console.log(err);
}
});
}
listen(app.portNumber);
The function listen is recursively calling itself. In case of port busy error. Incrementing the port number each time.
update Completely re-done
Callback full version
First of all this version is the one that follow the same signature as nodejs http.Server.listen() method!
function listen(server) {
const args = Array.from(arguments);
// __________________________________ overriding the callback method (closure to pass port)
const lastArgIndex = arguments.length - 1;
let port = args[1];
if (typeof args[lastArgIndex] === 'function') {
const callback = args[lastArgIndex];
args[lastArgIndex] = function () {
callback(port);
}
}
const serverInstance = server.listen.apply(server, args.slice(1))
.on('error', function (err) {
if(err.errno === 'EADDRINUSE') {
console.log(`----- Port ${port} is busy, trying with port ${port + 1} -----`);
port += 1;
serverInstance.listen.apply(serverInstance, [port].concat(args.slice(2, lastArgIndex)));
} else {
console.log(err);
}
});
return serverInstance;
}
Signature:
listen(serverOrExpressApp, [port[, host[, backlog]]][, callback])
just as per
https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback
The callback signature is changed to
(port) => void
usage:
const server = listen(app, 3000, (port) => {
console.log("server is running on port :" + port);
});
// _____________ another example port and host
const server = listen(app, 3000, 'localhost', (port) => {
console.log("server is running on port :" + port);
});
Explanation
In contrary to the old example! This method doesn't call itself!
Key elements:
app.listen() first call will return a net.Server instance
After binding an event once, calling listen again into the same net.Server instance will attempt reconnecting!
The error event listener is always there!
each time an error happen we re-attempt again.
the port variable play on the closure to the callback! when the callback will be called the right value will be passed.
Importantly
serverInstance.listen.apply(serverInstance, [port].concat(args.slice(2, lastArgIndex)));
Why we are skipping the callback here!?
The callback once added! It's hold in the server instance internally on an array! If we add another! We will have multiple triggers! On the number of (attempts + 1). So we only include it in the first attempt!
That way we can have the server instance directly returned! And keep using it to attempt! And it's done cleanly!
Simple version port only
That's too can help to understand better at a glimpse
function listen(server, port, callback) {
const serverInstance = server.listen(port, () => { callback(port) })
.on('error', function (err) {
if(err.errno === 'EADDRINUSE') {
console.log(`----- Port ${port} is busy, trying with port ${port + 1} -----`);
port += 1;
serverInstance.listen(port);
} else {
console.log(err);
}
});
return serverInstance;
}
Here the parameter port variable play on the closure!
ES6 full version
function listen(server, ...args) {
// __________________________________ overriding the callback method (closure to pass port)
const lastArgIndex = args.length - 1;
let port = args[0];
if (typeof args[lastArgIndex] === 'function') {
const callback = args[lastArgIndex];
args[lastArgIndex] = function () {
callback(port);
}
}
const serverInstance = server.listen(server, ...args)
.on('error', function (err) {
if(err.errno === 'EADDRINUSE') {
console.log(`----- Port ${port} is busy, trying with port ${port + 1} -----`);
port += 1;
serverInstance.listen(...[port, ...args.slice(1, lastArgIndex)])
} else {
console.log(err);
}
});
return serverInstance;
}
Why the old version is bad
To say right it's not really! But with the first version! We call the function itself at every failure! And each time it create a new instance! The garbage collector will budge some muscles!
It doesn't matter because this function only execute once and at start!
The old version didn't return the server instance!
Extra (for #sakib11)
You can look at #sakib11 comment to see what problem he fall in! It can be thoughtful!
Also in the comment i mentioned promise version and closure getter pattern! I don't deem them interesting! The way above just respect the same signature as nodejs! And too callback just do fine! And we are getting our server reference write away! With a promise version! A promise get returned and at resolution we pass all the elements! serverInstance + port!
And if you wonder for the closure getter pattern! (It's bad here)
Within our method we create a ref that reference the server instance! If we couldn't return the server instance as we are doing (imaging it was impossible! So each time a new instance is created! The pattern consist of creating a closure (method at that scope) And return it!
so for usage
const getServer = listen(port, () => {
console.log('Server running at port ' + getServer().address().port);
const io = socketIo(getServer(), {});
});
But it's just overhead specially we need to wait for the server to be done!
Unless we set it in a way that it use a callback! or return a promise!
And it's just over complicating! And not good at all!
It's just because i mentioned it!
And the method above can be tweaked! To add number of attempts limit! And add some events or hooks! But well! Generally we only need a simple function that just attempt and make it! For me the above is more then sufficient!
Good links
https://nodejs.org/api/http.html#http_http_createserver_options_requestlistener
https://nodejs.org/api/http.html#http_class_http_server
https://expressjs.com/en/4x/api.html#app.listen
From the doc
The app.listen() method returns an http.Server object and (for HTTP) is a convenience method for the following:
app.listen = function () {
var server = http.createServer(this)
return server.listen.apply(server, arguments)
}