2

I am trying to repair my relationship with JavaScript after a long time of conflict :D

In the following code :

class e extends Error{
    constructor(message,id){
        super(message)
        this.id = id
    }
}

const e1 = new e('message',12);
const e2 = {...e1}

console.log(e2);
}

the result is {id:12}

As you can see the message property is not there ,I understand that this happening because the message property is not owned by e2.

My question is what is the best way to make it so such that the result is {id:12,message:'message'}

I understand that it may be very novice question, but I appreciate your elaboration on it

use case

I have a global error handler below I need to copy err object into error object if in production mode, but I found that I need to manually add this line error.message = err.message;

I was wondering if there is away to clone err object without manually add this line i.e. what is the best way of cloning an object without knowing its super class properties?

module.exports = (err, req, res, next) => {

  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';
  console.log(process.env.NODE_ENV)

  if (process.env.NODE_ENV === 'development') {
    sendErrorDev(err, res);
  } else if (process.env.NODE_ENV === 'production') {
    let error = { ...err };
    error.message = err.message;
   // console.log(err)

    //   if (error.name === 'CastError') error = handleCastErrorDB(error);
    if (error.code === 11000) error = handleDuplicateFieldsDB(error);
    //   if (error.name === 'ValidationError')
    //     error = handleValidationErrorDB(error);

    sendErrorProd(error, res);
  }
};
Saed Nabil
  • 6,705
  • 1
  • 14
  • 36
  • 1
    Maybe `this.message` isn't enumerable? `Object.keys(e1) // ['id']` makes me think it's not. So it would be missed out by the spread operator. – evolutionxbox Oct 28 '20 at 21:42
  • @evolutionxbox yes, I understand that e2.hasOwnProperty('id') -> true but e2.hasOwnProperty('message') -> false, I want to know How if possible to make the spread operator work – Saed Nabil Oct 28 '20 at 22:03
  • Message needs to become an enumerable property. That might mean that you can’t inherit from Error. – evolutionxbox Oct 28 '20 at 22:05
  • @evolutionxbox 'Message needs to become an enumerable property ' How can I do that? and I am inheriting from the error class of JavaScript what do you mean by can't inherit from it ? – Saed Nabil Oct 28 '20 at 22:13
  • 1
    Honestly I would write an answer if I knew how. I know `Object.defineProperty` will help. — what’s the reason from inheriting from Error if you can’t use the properties it defines? Or maybe using spread as a means of inheritance is not good? – evolutionxbox Oct 28 '20 at 22:20
  • Appreciate your help ,I just want to make my own error subclass which have to inherit from Error class ,I am actually using spread operator to copy e1 into e2. I think may be there is another way to do so such that e2 will have message property available – Saed Nabil Oct 28 '20 at 22:27
  • What is your case exactly? An independent copy of an instance requires to run a constructor (manually constructing an object may work for simple classes like Error and e that don't do much in a constructor but it's not an option for arbitrary class). If you need to clone instances regularly, add a method to custom class, `clone() { return new e(this.message, this.id) }`. – Estus Flask Oct 28 '20 at 23:35
  • @EstusFlask thank you for your help. I added my use case check the question again please. – Saed Nabil Oct 29 '20 at 00:27
  • For random object, there's no way to clone "without knowing its super class properties". Some class instances aren't workable without `new`, and their properties may be a result of internal state that cannot be reproduced just by copying all properties, e.g. `Map`, so you need to be aware of a class to be able to make a copy. – Estus Flask Oct 29 '20 at 07:30
  • 1
    From what you listed, it's not obvious that you need to clone it. It's error message that is important in the end. For arbitrary error, you may want to construct it based on previous error, `new Error(oldErr.message)`. You can store original error(s) in `errors` field in case you need it later for reference with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError – Estus Flask Oct 29 '20 at 07:36
  • @EstusFlask that is a cool idea to instantiate new custom error based on the actual error , thank you very much. – Saed Nabil Oct 29 '20 at 19:14

2 Answers2

3

As established in the comments, this has less to do with inheritance and more with message not being enumerable. It's still an own property of each instance.

You can change that in your custom subclass though with Object.defineProperty:

class E extends Error{
    constructor(message,id){
        super(message)
        Object.defineProperty(this, 'message', {enumerable: true})
        this.id = id
    }
}
const e = new E('message', 12);
console.log({...e});

This will cause the spread syntax in the object literal to copy the property. However, if your goal is to get a new instance with the same custom prototype, you cannot use spread syntax. Write a method instead:

class E extends Error{
    constructor(message,id){
        super(message)
        this.id = id
    }
    clone() {
        return new E(this.message, this.id)
    }
}
const e = new E('message', 12);
console.log(e.clone());
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Ok , this is great , I think this is almost what I was looking for ,thank you, but why in my code `console.log(e2.hasOwnProperty('message'))` gives false if as you said " It's still an own property of each instance."? – Saed Nabil Oct 29 '20 at 00:37
  • @SaedNabil Because `e2` is the object literal you created without the property, not an `e` instance. `e1.hasOwnProperty('message')` will be `true`. – Bergi Oct 29 '20 at 00:37
  • It's still an own property of each instance - in your phrase what do you mean by each instance ? you mean you don't consider e2 as instance ? is this what you mean? – Saed Nabil Oct 29 '20 at 00:42
  • 1
    An instance is an object that inherits from the respective prototype (and/or was constructed by the respective constructor). The phrase "*each instance*" meant "*each instance of `e` (or `Error`)*". `e1 instanceof e` and `Object.getPrototypeOf(e1) == e.prototype`, but `e2` is at best an `Object` instance. It is not an `e` or `Error` instance. – Bergi Oct 29 '20 at 01:13
1

The best way to set up a prototype is to not do this with object spread as object literal produces a plain object that inherits from Object.

A way to fix this is:

Object.setPrototypeOf(e2, e)

A more correct way to do this is to create an object with proper prototype:

e2 = Object.create(e1)

To shadow inherited properties on assignment it can be combined with Object.assign to provide them as a plain object.

None of these will produce {id:12,message:'message'} output. message is non-enumerable property declared on Error.prototype that is used by Error.prototype.toString to stringify an error.

If the intention is to not use prototypal inheritance but produce {id:12,message:'message'} plain object, it needs to be:

e2 = {...e1, message: e1.message}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • see also the mdn ref https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error –  Oct 28 '20 at 23:26