Error: "Can not set headers after they are sent"

1

In a controller I am using promises because I perform multiple queries and I want to avoid having a multitude of callbacks, if you do not find a value must send a message to the client and not pass to the next promise.

This is my code:

  var body        = req.body;
  var params      = req.params;
  var queryParams = req.query;

  var origin =  req.get('source-type') || req.get('user-agent');

  var orderData = new orderInterface(origin);
  orderData.setOrigin(origin);

  var historyData = new historyInterface();

  usersModel
  .findOne({ 'shopcart._id' : body.shopcart }, { 'password': false, 'credit': false })
  .exec()
  .then(function(user){

    var address = body.address;
    orderData.setUser(user.toObject());

    if(_.isObject(address)){
      address.user = user._id;
      return new addressModel(address).save();
    }else{
      return addressModel.findOne({ '_id': address }).exec();
    }
  })
  .then(function(address){

    orderData.setAddress(address);

    if (_.isEmpty(address.latitude) || _.isEmpty(address.longitude)){
        res.status(200).json(message.custom('No have coordinates in address'));
    }else{
      return establishmentsModel
      .findCoordinates([ address.latitude, address.longitude ])
      .populate('items.product')
      .exec();
    }
  })
  .then(function(establishments){

    var shopcart = orderData.shopcart;

    if(_.isEmpty(shopcart) || _.isEmpty(establishments)){
      var msn = 'Establishments not have coverage';

      if(_.isEmpty(shopcart)){
        msn = 'problem with the shopcart';
      }

      res.status(200).json(message.custom(msn));
    }else{

      shopcart.products.forEach(function(product){

        if(product.items){

          var item = establishments.items.id(product.items);

          if(item){

            orderData.addProducts(item, product.quantity);
            establishments.items[item.__index].buy      += product.quantity;
            establishments.items[item.__index].quantity -= product.quantity;

          }else{
            res.status(200).json(message.custom('produc outoff stablishments' + product.string()));
          }
        }else{
          res.status(200).json(message.custom('product without item' + product.string()));
        }

      });

      orderData.setEstablishments(establishments);
      return establishments.save();

    }

  })
  .then(function(establishments){

    var coupon = orderData.shopcart.coupon;

    var options = {
      code   : coupon || '',
      userId : orderData.user._id
    };

    return referralsModel.searchAndUse(options);
  })
  .then(function(result){

    var options = {};

    if(result.referrals){

      orderData.setDiscount(result.discount);
      historyData.setCoupon(result.referrals);

    }else{
      options.code  = result.coupon;
    }

    options.establishments = orderData.establishments._id;
    options.items          = orderData.items;
    return couponsModel.searchAndUse(options);

  })
  .then(function(result){

    if(result.promotion){
      historyData.setCoupon(result.promotion);
    }
    else if(_.isEmpty(result)){
      historyData.setItems(orderData.items);
      historyData.setCoupon(result.coupon);
    }

    if(result.discount){
      historyData.setDiscount(result.discount);
      orderData.setDiscount(result.discount);
    }

    orderData.addState('', new Date());
    orderData.addPay(body.pay);

    return new ordersModel(orderData.get('save')).save();

  })
  .then(function(order){

    historyData.setTime(orderData.order.create);
    historyData.setUser(orderData.user._id);

    if(historyData.get().save !== 0){
      orderData.setId(order._id);
      return historyModel.save(historyData.get());
    }else{
      if(params.userId){
        orderData.setId(order._id);
        order = orderData.get('send');
      }
        res.status(201).json(order);

    }
  })
  .then(function(history){
    var orderSend = orderData.get('send');
    res.status(201).json(orderSend);

  })
  .catch(function(err){
    console.log(err);
    next(err);
  });

When I run the test he sends me the following error twice:

[Error: Can't set headers after they are sent.]
[Error: Can't set headers after they are sent.]
    
asked by soldat25 20.01.2016 в 18:04
source

1 answer

2

The problem that your program has can be divided into two parts:

The first is that you are not breaking the chain of promises properly. These are going to continue running because if you have the following

 promise.then(function(resultado1) {
     if (condition) {
         hacer_algo();
     } else {
         return new Promise();
     }
 }).then(function(resultado2) {
     // ¿Si la condicion se cumple cual es el valor de resultado2?
 })

If the condition is met what will happen is that Javascript will "add" a return undefined instruction to the end of your then block as specified in the documentation . This is a translation:

  

By default the functions return undefined . To return another value the function must have a return statement that specifies which is the value to be returned.

You only return a value if the condition is not met; so in case the return value is fulfilled is undefined and this for a promise what it means is that resolve with the value undefined . The string will continue to run until an error ends or an error occurs in which case the promise will be rejected and the error branch will be skipped.

The second part of the problem is that starting from the above you possibly end up calling several times to res.status(200).json(..) which sends information to the client because res.json() is equivalent to res.send() and this modifies the response headers assigning Content-Lenght , Content-Type among others and immediately sends a response to the client hence the error that is printing you.

For what you are trying to do, you have to use one of the following strategies known as bran . You can find more information in the following SO links in English.

Bluebird promises and catch branching

Proper way to skip a then function in q promises

Break promise chain and call to function based on the step in the chain where it is broken (rejected)

How to properly abort to node.js promise chain using Q?

However, I'll give you some examples of what you want to do.

Branch

promise.then(function() {
    if (condicion) {
        hacer_algo();
    } else {
        return promise2.then(function() {
            if (otra_condicion) {
                return promise3.then(function() {
                    otro_procesamiento();
                })
            } else {
                hacer_otra_cosa();
            }
       })
}).catch(function() {
   // Manejo de errores
});

This creates some nesting but it is totally valid. Keep in mind that I return several promises to only have to use a catch for error handling. To avoid nesting in the branch you can use other functions. This is the equivalent of the above.

function path2() {
    return promise2.then(function() {
        return path3();
    });
}

function path3() {
    if (otra_condicion) {
        return promise3.then(function() {
            otro_procesamiento();
        });
    } else {
        hacer_otra_cosa();
     }
}

promise.then(function() {
   if (condicion) {
       hacer_algo();
   } else {
       return path2();
   }
}).catch(function() {
    // Manejo de errores
});

You will find many examples that use error handling as flow control. I personally do not like that variant very much, but it is completely valid and I have usually seen it translated as interruption or ruptura

Rupture

promise.then(function() {
    if (condicion) {
        hacer_algo();
        throw new CustomError('mensaje identificador 1');
    } else {
        return promise2
    }
})
.then(function() {
    if (otra_condicion) {
        return promise3
    } else {
        hacer_otra_cosa();
        throw new CustomError('mensaje identificador 2')
    }
})
.then(function() {
    otro_procesamiento();
})
.catch(function(err) {
    if (err instanceof CustomError) {
        //if (err.message === 'mensaje identificador 1') {
        //    procesamiento posterior
        //}
        ...
    } else {
        // manejo de errores
    }
});
    
answered by 20.01.2016 / 22:37
source