Handling event responses and failures
Real systems in production need to be able to handle cases where the system or connection fails; for this reason, you may sometimes want to specify a callback when emitting events across a network; that way you can track individually whether or not an event was successfully delivered to the other side of the connection. Another use case is that you may simply want to pass data back as responses to specific emitted events.
Event-related methods in SC accept a 'callback' function as final argument which allow you to check when the action has been acknowledged by the server. When a method fails, the callback will receive an error as argument — In this case you may wish to retry calling the method or you can display a message to the user to let them know about the issue.
Here is how to handle various responses and errors (sample code):
Client-side
Emitting
// Client code
socket.emit('ping', 'This is a ping', function (err, responseData) {
if (err) {
// Failed to emit the event; retry or let the user know and keep going?
// You can check err.name to see what kind of error it is.
// Errors generated by SC have a special err.name.
// In the case of a lost connection, SC will send back a
// BadConnectionError err such that `err.name == 'BadConnectionError'`.
// If the response times out (E.g. for other reasons not related to
// connection) then SC will pass a TimeoutError err such
// that `err.name == 'TimeoutError'`.
// These are the only two kinds of errors sent back by SC, all other
// error types will be custom application-level errors which you
// can specify yourself.
} else {
// Event was emitted successfully
}
});
Note that the emit method will never fail if you do not pass the callback argument to it - In such case, the call to emit would treat the event as low-importance (volatile). This feature may be useful if you're emitting an event multiple times per second and do not care if it fails occasionally. When you pass a callback to the emit method, you will have to explicitly send a response from the other side (otherwise the callback will time out):
// Server code
socket.on('ping', function (data, res) {
// ...
if (success) {
// Send back a success message (can be any JSON-compatible object/type).
// Note that the first argument is reserved for errors while the second
// can be used to pass back custom objects.
// The responseData parameter in the previous code snippet will be 'Success'.
res(null, 'Success');
} else {
var err = new Error('Failed to do stuff...');
// As a convention, it may be a good idea to give each
// error a name. That way on the client side you can quickly
// check the error type with: if (err.name === 'MyCustomError')
err.name = 'MyCustomError';
// If you like, you can also add custom properties to the err object.
// and they will be accessible on the client side.
res(err); // Send back error
}
});
The first argument to the res function is an error (can be any JSON-compatible type or object; but an instance of the Error object is recommended). If you want to send back a regular message (no error), you should set the first argument to null. If you pass an Error object as the first argument to res(err), the error will be dehydrated on the server and rehydrated on the client side; all properties (except for the stack property) will be accessible on the client. The error's stack property will not be sent to the client for security reasons but you can log the error on the server-side (the stack is visible from the server).
Publishing
// Client code
socket.publish('pong', 'This is a pong', function (err) {
if (err) {
// Failed to publish event, retry or let the user know and keep going?
} else {
// Event was published successfully
}
});
Subscribing
// Client code
var pongChannel = socket.subscribe('pong');
pongChannel.on('subscribeFail', function (err, channelName) {
// Handle subscribe failure
});
pongChannel.on('subscribe', function (channelName) {
// Handle subscribe success
});
// Handle data published to the pong channel
pongChannel.watch(function (data) {
// This gets executed whenever data is published to the pong channel
});
Server-side
Emitting
// Server code
socket.emit('ping', 'This is a ping', function (err, responseData) {
if (err) {
// Failed to emit event, retry or log?
} else {
// Event was emitted successfully
}
});
If we provide a callback, we will need to make sure the response is sent on the client:
// Client code
socket.on('ping', function (data, res) {
// ...
if (success) {
res(null, 'Success'); // Send back success message
} else {
var err = new Error('Failed');
err.name = 'ItFailedError';
// Attach an object to a custom property to send along with the error.
err.customProperty = {foo: 'bar'};
res(err); // Send back error
}
});
Publishing
// Server code
socket.exchange.publish('pong', 'This is a pong', function (err) {
if (err) {
// Failed to publish event, retry or log?
} else {
// Event was published successfully
}
});