Search code examples
node.jsexpresseventsevent-handlingeventemitter

Event emitter doesn't work properly after first request


I have a basic controller in my express.js application. And I try to execute a certain service that fetch mock data after small delay. This service is inherited from EventEmitter, and emits SUCCESS event after the data was received.

Here is my controller:

const express = require('express');
const router = express.Router();
const GetAllUsers = require('../GetAllUsers');
const getAllUsers = new GetAllUsers();

router.get('/', function(req, res, next) {
  getAllUsers
    .on('SUCCESS', (users) => {
      res
        .status(200)
        .json({ users });
    })
    .on('ERROR', next);

  getAllUsers.execute();
});

module.exports = router;

And service:

const EventEmitter = require('events');

class GetAllUsers extends EventEmitter {
  async execute() {
    const data = [{ id: 1, name: 'user 1' }, { id: 2, name: 'user 2' }];

    try {
      const users = await new Promise(resolve => {
        setTimeout(() => {
          resolve(data);
        }, 1000);
      })

      this.emit('SUCCESS', users);
    } catch (error) {
      this.emit('ERROR', error);
    }
  }
}

module.exports = GetAllUsers;

The problem is that when I enter on the /users path for the first time I actually get a list of users. But when I try the second time and subsequent, I get the following error:

Cannot set headers after they are sent to the client
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:471:11)
    at ServerResponse.header (/home/debian/dev/sandbox/emitterTest/node_modules/express/lib/response.js:767:10)
    at ServerResponse.send (/home/debian/dev/sandbox/emitterTest/node_modules/express/lib/response.js:170:12)
    at ServerResponse.json (/home/debian/dev/sandbox/emitterTest/node_modules/express/lib/response.js:267:15)
    at GetAllUsers.getAllUsers.on (/home/debian/dev/sandbox/emitterTest/routes/users.js:12:10)
    at GetAllUsers.emit (events.js:187:15)
    at GetAllUsers.execute (/home/debian/dev/sandbox/emitterTest/GetAllUsers.js:14:12)

As far as I understand, headers are set somewhere before I send my response.

And when I trying to implement my controller without emitter, everything works fine:

router.get('/', async function(req, res, next) {
  const data = [{ id: 1, name: 'user 1' }, { id: 2, name: 'user 2' }];

  const users = await new Promise(resolve => {
    setTimeout(() => {
      resolve(data);
    }, 1000);
  })

  res
    .status(200)
    .json({ users });
});

How can this problem be solved?


Solution

  • This happens because every time you make a request, you run your router function:

    function(req, res, next) {
      getAllUsers 
        .on('SUCCESS', (users) => { //subscribe to event every time
          res
            .status(200)
            .json({ users });
        })
        .on('ERROR', next);
    
      getAllUsers.execute();
    }
    

    You got subscribed to those event and run it once. At the time, you made a second request, you got 2 subscribers : from previous request, and from current. And then, the execute() function fires the event. So there's 2 functions executed at a time, and one of them, tries to send response again, but that is impossible, therefore it causes an error.

    If those event emitter is really needed, you can subscribe to fire event only once by using the getAllUsers.once function.