Barış Uşaklı
Barış Uşaklı

Reputation: 13532

How to pass error object to socket.io callback

I am using callbacks with socket.io

Client code :

socket.emit('someEvent', {data:1}, function(err, result) {
    console.log(err.message); 
});

Server code :

socket.on('someEvent', function(data, callback) {
    callback(new Error('testing error'));
});

With the above code the client side always prints out undefined. If I change the server side code to the following I can see the error message.

socket.on('someEvent', function(data, callback) {
    callback({message:'testing error'});
});

I can pass my own custom objects to the client just fine, just not the error object. Any ideas?

Upvotes: 13

Views: 8948

Answers (3)

luff
luff

Reputation: 3444

Here's a little router function that works with plain error objects. You might want to remove the stack variable if you're shy.

socket.on('api', async (method, args, callback) {
  try {
    const result = await api[method](...args)
    callback(null, result)
  } catch (error) {
    const { name, message, stack } = error
    callback(error.toJSON ? error : { name, message, stack })
  }
})

Upvotes: 0

Iain Collins
Iain Collins

Reputation: 6884

I also think it's odd that Socket.IO doesn't seem to provide explicit built in support for passing Error objects in a meaningful way.

If you want to have Error objects seralized correctly in Socket.IO callbacks you can define a method like this to specify how serialization with .toJSON for Error messages should be handled:

if (!('toJSON' in Error.prototype)) {
    Object.defineProperty(Error.prototype, 'toJSON', {
    value: function () {
      let result = {}
      Object.getOwnPropertyNames(this).forEach((key) => {
        if (key === 'stack') return
        result[key] = this[key];
      }, this)
      return result
    },
    configurable: true,
    writable: true
  })
}

If you are throwing messages from the server you will want to define this on the server, and if throwing errors from a client to the server you will need to define it on the client too.

Note: In this example it strips the 'stack' property when returning errors to the client (to avoid exposing internals to a client), but this may be something worth leaving in, at least for development mode.

Update: This is a lot easier with ES6

Adapted from a blog post I wrote recently on this:

https://medium.com/@iaincollins/error-handling-in-javascript-a6172ccdf9af

I'm happy to say this easier/cleaner with ES6.

If you define your class extending Error like this:

class ValidationError extends Error {
  constructor(message) {
    super(message)
    this.name = 'ValidationError'
    this.message = message
  }

  toJSON() {
    return {
      error: {
        name: this.name,
        message: this.message,
        stacktrace: this.stack
      }
    }
  }
}

Instead of this being the object that gets passed back:

{ name: 'ValidationError' }

It now looks something like this:

{ 
  error: {
    name: 'ValidationError',
    message: 'A validation error',
    stacktrace: '…'
  }
}

You can see what to expect by doing this:

console.log(JSON.stringify(new ValidationError('A validation error'))

Upvotes: 1

loganfsmyth
loganfsmyth

Reputation: 161457

socket.io data is serialized as JSON, which can only represent plain objects. You will need to serialize any errors into a recognizable plain-object format, or let Socket.IO's standard serialization do its thing (which will result in a plain empty object for Error instances.

Upvotes: 14

Related Questions