Sharath
Sharath

Reputation: 2428

Unhandled rejection Error: Can't set headers after they are sent. Node.js

When trying run below code I get Unhandled rejection Error: Can't set headers after they are sent when return GetData(); is invoked it starts execution of the function but immediately returns the control back with the error. Observed during debugging.

Basically the code tries to fetch key from MySQL DB if doesn't exist in redis DB

All database and redis related modules are written in separate file so as to reuse it.

somefile.js

var express = require('express');
var router = express.Router();
var dbModules = require('../common/database');
var redisModules = require("../common/redismodule");

function getSettings(request, response) 
{
    return GetData();

    function GetData() 
    {
        return redisModules.GetRedisValue("key")
        .then(function (result) 
        {
            if (!result) 
                return SetData();      
            else 
                return result;
        })
        .then(function (result) 
        {
            response.status(200).send({ value : result });
        })
        .catch(function (e) 
        {
            response.status(500).send();
        };
    }

    function SetData()
    {
        return dbModules.executeQuery('query')
        .then(function(results) 
        {
            // some code
            return 'some_key';
        })
        .then(function (result) 
        {
            redisModules.setRedisValue('key', result);
        });
    }
}

database.js

File for handling database connections

var mysql = require('promise-mysql');

pool = mysql.createPool({
  host: '',
  user: '',
  password: '',
  database: '',
  connectionLimit: 4
});


module.exports = {

  getSqlConnection: function()
  {
      return pool.getConnection().disposer(function(connection) 
      {
          console.log("came here in releasing connection function");
          pool.releaseConnection(connection);
      });
  },

  executeQuery: function(sqlQuery)
  {
      return Promise.using(module.exports.getSqlConnection(), function(connection) 
      {
          return connection.query(sqlQuery)
          .then(function(results) 
          {
              return results;
          });
      });
  }
};

redismodule.js

File for handling redis get, set concepts

var Promise = require('bluebird');
var constants = require('../common/contants');

var redisClient;        // Global (Avoids Duplicate Connections)

module.exports = 
{
    OpenRedisConnection : function()
    {
        if (redisClient == null) 
        {
            redisClient = require("redis").createClient(6379, 'localhost');
        }
    },
    isRedisConnectionOpened : function()
    {
        if (redisClient && redisClient.connected == true) 
        {
            return true;
        }
        else 
        {
            if(redisClient)
                redisClient.end();  // End and open once more

            module.exports.OpenRedisConnection();
            return true;
        }
    },
    GetRedisValue: function (key) 
    {
        return new Promise(function (resolve, reject)
        {
            if(!module.exports.isRedisConnectionOpened())
                 reject("Redis connection failure");

            redisClient.get(key, function (error, result) 
            {
                if (error) 
                {
                    reject(error);
                }
                else 
                {
                    if (result == null)
                        resolve();    // Key not present so create
                    else
                        resolve(result);
                }
            });
        }); 
    },
    SetRedisValue: function (key, value) 
    {           
        return new Promise(function (resolve, reject)
        {
            if(!module.exports.isRedisConnectionOpened())
                 reject("Redis connection failure");

            redisClient.set(key, value, 'EX', 1000, 
            function(err,reply) 
            {
                if (reply == 'OK')
                    resolve(value);         // Send the value
                else
                    reject(err);
            });
        }); 
    }
};

The execution starts when getSettings function is called.

I have just included all the code so that if its correct it might be useful for others.

Corrected Answer

somefile.js

var Promise = require('bluebird');
var dbModules = require('database');
var redisModules = Promise.promisifyAll(require("redismodule"));

async function getSettings(request, response) {

    try {
        var data = redisModules.GetRedisValue("key");
        if (!data)
            data = await SetData();

        return response.status(200).send({
            value: data
        });

    } catch (error) {
        return response.status(500).send({
            'error': 'Try after some time'
        });
    }    

    function SetData() {
        let result = dbModules.executeQuery('query')
        return redisModules.setRedisValue('key', result);
    }
}

database.js

var mysql = require('promise-mysql');
var pool = mysql.createPool({
    host: '',
    user: '',
    password: '',
    database: '',
    connectionLimit: 4
});

function getSqlConnection() {
    return pool.getConnection().disposer(function (connection) {
        console.log("came here in releasing connection function");
        pool.releaseConnection(connection);
    });
}

module.exports = {
    executeQuery: function (sqlQuery) {
        return Promise.using(getSqlConnection(), function (connection) {
            return connection.query(sqlQuery)
                .then(function (results) {
                    return results;
                });
        });
    }
};

redismodule.js

var redisClient; // Global (Avoids Duplicate Connections)

// Making the below functions are private
function openRedisConnection() {
    if (redisClient && redisClient.connected == true) {
        return;
    } else {
        if (redisClient)
            redisClient.end(); // End and open once more

        redisClient = require("redis").createClient(6379,
            process.env.REDIS_URL, {
                auth_pass: process.env.REDIS_PASS
            });
        redisClient.selected_db = 1;
    }
}

module.exports = {
    GetRedisValue: function (key) {
        openRedisConnection();

        redisClient.get(key, function (error, result) {
            if (error) {
                return error;
            } else {
                if (result)
                    return result;
                else
                    return null;
            }
        });
    },
    SetRedisValue: function (key, value) {

        openRedisConnection();

        redisClient.set(key, value, 'EX', 1000,
            function (err, reply) {
                if (reply == 'OK')
                    resolve(value); // Send the value
                else
                    reject(err);
            });
    }
};

Upvotes: 0

Views: 1105

Answers (1)

Roamer-1888
Roamer-1888

Reputation: 19288

This is the way I see it :

somefile.js

var dbModules = require('../common/database');
var redisModules = require("../common/redismodule");

function getSettings(request, response) {
    function getData() {
        return redisModules.getRedisValue('key')
        .then(function (result) {
            return result || setData();
        });
    }

    function setData() {
        return dbModules.executeQuery('query')
        .then(function(results) {
            return redisModules.setRedisValue('key', results);
        });
    }

    return getData()
    .then(function(result) {
        response.status(200).send({ value: result });
    }).catch(function (e) {
        response.status(500).send();
    });
}

database.js

var mysql = require('promise-mysql');
var pool = mysql.createPool({
    host: '',
    user: '',
    password: '',
    database: '',
    connectionLimit: 4
});

function getSqlConnection() {
    return pool.getConnection().disposer(function(connection) {
        console.log("came here in releasing connection function");
        pool.releaseConnection(connection);
    });
}

module.exports = {
    'executeQuery': function(sqlQuery) {
        return Promise.using(getSqlConnection(), function(connection) {
            return connection.query(sqlQuery);
        });
    }
};

redismodule.js

var Promise = require('bluebird');
var redis = Promise.promisifyAll(require('redis'));
var redisClient = null;

function openRedisConnection() {
    if (!redisClient || !redisClient.connected) {
        if (redisClient) {
            redisClient.end(); // End and open once more
        }
        redisClient = redis.createClient(6379, process.env.REDIS_URL, {
            auth_pass: process.env.REDIS_PASS
        });
        redisClient.selected_db = 1;
    }
    return redisClient;
}

module.exports = {
    'getRedisValue': function(key) {
        return openRedisConnection().getAsync(key); // here we call the promise-returning .getAsync() method, created by Promise.promisifyAll()
    },
    'setRedisValue': function(key, value) {
        return openRedisConnection().setAsync(key, value, 'EX', 1000); // here we call the promise-returning .setAsync() method, created by Promise.promisifyAll()
    }
};

My main contributions are in somefile.js and redismodule.js. The third module, database.js has been tidied but nothing more than that.

Things like dbModules.executeQuery('query') and redisModules.getRedisValue('key') need to be addressed but I guess you know what you are doing there.

Upvotes: 1

Related Questions