I am perplexed as to what is happening in this scenario
Client:
socket.emit('ferret', 'tobi', function (data) {
console.log(data); // data will be 'woot'
});
Server:
io.on('connection', function (socket) {
socket.on('ferret', function (name, fn) {
fn('woot');
});
});
This is from the docs. How does it make any sense that a function is being passed to the server for the callback? How can the server be calling a client function? I am very confused.
It's obvious that you can't directly call a function on the client from the server.
You can easily do this indirectly, though:
When the client sends the ferret message, it stores the given function locally with an ID.
The client sends this ID along with the message to the server.
When the server wants to call the client function, it sends a special message back with the ID and the arguments to the function.
When the client recieves this special message, it can look up the function by its ID and call it.
I do not know if this is exactly what Socket.io does, but it's reasonable to assume that it's something similar to this.
Edit: Looking at the source code (here and here), this does indeed seem to be pretty much what Socket.io does.
The third argument to the emit method accepts a callback that will be passed to the server so that you can call in acknowledgement with any data you wish. It's actually really convenient and saves the effort of having paired call-response events.
Acknowledge is the way to get a response corresponding to a sent message. The following is a code snippet for server-side.
io.sockets.on('connection', function(socket) {
socket.on('echo', function(data, callback) {
callback(data);
});
});
Related
Issue clarification
When we use .emit() or .send() and we also want to confirm message reception (so called acknowledgements) we simply write something like this:
socket.emit('someEvent', payload, callback);
What this question is all about is a callback part. That's the great stuff as it allows to generally send back some data as a response with no extra events emitted. All that server needs to do is to handle the request in a proper way:
socket.on('someEvent', (payload, callback) => {
doSomeStuff();
callback(someData);
);
That works just fine when we deal with a success case. But what shall we do in these cases:
1) Callback was not sent from the client side / callback's not a function and there's a need to respond from the server side with something like 'Error: no callback is provided. Usage: ...'
Example:
Client side - socket.emit('someEvent'); or socket.emit('someEvent', 1);
Server side - socket.on('someEvent', callback => callback());
or
2) While handling the request something went wrong (e.g. an unsuccessful validation result) and we need to report this in a way like: 'No payload is provided or it is invalid'
Example:
Server side -
socket.emit('someEvent', payload, callback => {
checkPayload();
callback(someData);
});
Client side - socket.on('someEvent', invalidPayload, callback);
Question: is there a mechanism to create custom callback from responder's side?
My workings and workarounds
1) As for the missing callback or that one which is not a function I've concluded that I can only validate it and then invoke it only in case of its validity. So the server side is undergoing some changes:
socket.emit('someEvent', callback => callback instanceof Function && callback()); //check callback correctness
Pros: there won't be an internal error if a callback is not a function as expected.
Cons: in case of invalid callback a client won't be noticed about it.
2) As for the case when we need to send some error back I've only found a workaround to return a specific, agreed in advance, falsy value like null so that it means that no data can be returned.
socket.emit('someEvent', payload, callback => {
checkPayload();
callback(someData || null); //send falsy, error-like value instead
});
Pros: a client will be noticed about some error by getting null.
Cons: from server side there's no simple middleware function that validates the input data and returns error before the main logic is being executed.
I've thought about middlewares for reaching the needed functionality, but there's no, so to say, 'event level middlewares' yet, only on the whole namespace and socket levels. Shall I try to filter events by their names on the socket level to attach the needed functionality and send error in a way like next(new Error(...));? In this case there can be a work with error event listening, I guess.
socket.io / socket.io-client versions used: 2.3.0
1) Callback was not sent from the client side / callback's not a function and there's a need to respond from the server side with something like 'Error: no callback is provided. Usage: ...'
The client and server have to agree how to do this. If the client doesn't provide a callback, then the server argument will be undefined so you can detect that from the server.
So, the proper way to do it is this:
// client
socket.emit('someMsg', someData, function(response) {
console.log(`Got ${response} from server`);
});
// server
io.on('connection', socket => {
socket.on('someMsg', (data, fn) => {
console.log(`Got data ${data} from client, sending response`);
// if client wants a response, send the response
if (fn) {
fn("got your data");
}
});
});
So, if the client does not pass the callback, then fn on the server side will be undefined. So, you are correct to test for that before calling it.
2) As for the case when we need to send some error back I've only found a workaround to return a specific, agreed in advance, falsy value like null so that it means that no data can be returned.
Yes, you have to agree in advance how to send an error back. The cleanest way to send an error back would probably be to wrap your response in an object and use a .error property on that object.
// client
socket.emit('someMsg', someData, function(response) {
if (response.error) {
console.log(`Got error ${response.error} from server`);
} else {
console.log(`Got data ${response.data} from server`);
}
});
// server
io.on('connection', socket => {
socket.on('someMsg', (data, fn) => {
console.log(`Got data ${data} from client, sending response`);
// if client wants a response, send the response
if (fn) {
// no error here
fn({error: null, data: "Got your message"});
}
});
});
What you're seeing here is that socket.io is not really a request/response type protocol and socket.io has tried to shoehorn in a bit of a response around which you have to build your own structure.
Or, you can send an error object if there's an error:
// server
io.on('connection', socket => {
socket.on('someMsg', (data, fn) => {
console.log(`Got data ${data} from client, sending response`);
// if client wants a response, send the response
if (fn) {
// send an error here
fn({error: new Error("xxx Error")});
}
});
});
From server side there's no simple middleware function that validates the input data and returns error before the main logic is being executed.
I don't really understand what you're trying to use middleware for or to validate? the only place this data is present is on your message handler so any server-side validation you want to do on what the client sent needs to be there. You can certainly do that validation before you've send a response.
Shall I try to filter events by their names on the socket level to attach the needed functionality and send error in a way like next(new Error(...));? In this case there can be a work with error event listening, I guess.
Socket.io doesn't work like Express and I don't really see why you'd try to make it work that way. There is no next() involved in receiving a socket.io message so I'm not sure what you're trying to do there. There is an option for middleware when the socket.io connection is first made, but not for subsequent messages sent over that connection.
Is there a way to send response from server even if no callback is provided from client side?
If the client does not provide a callback, then the only way to send a response back to the client would be to send another message. But, the whole point of sending a response is if you have a cooperating client that is listening and expecting a response so the client may as well use the callback if they want the response. If the client doesn't want the response and won't code anything to receive it, there's nothing you can do about that.
I am trying to build a messaging web app using socket io and redux, and this post comes across. I can't comment on the post to ask questions, so I am posting a new question asking about the answer in that post(sorry if I violate stackoverflow quideline, but there seems to be no way for me to comment.) The part I don't understand is
In socketClient.js
emit(event, data) {
return new Promise((resolve, reject) => {
if (!this.socket) return reject('No socket connection.');
return this.socket.emit(event, data, (response) => {
// Response is the optional callback that you can use with socket.io in every request. See 1 above.
if (response.error) {
console.error(response.error);
return reject(response.error);
}
return resolve();
});
});
}
I don't quite understand the callback funciton in this.socket.emit. From what I understand, the call back function will be executed on server side, when the server receive the event, data and callback funciton from client. If that's the case, what does the return do in the callback function? what should response be? and what should this.socket.emit return? and how can the server resolve a promise on client side?
I don't quite understand the callback function in this.socket.emit.
When you pass a callback as the third argument to socket.io's .emit() method, it tells socket.io that you want an acknowledgement that the server has received the message and an optional response back from the server. That callback will be called after the server has received your message and called a callback on its end and the server has the option of sending a response back with that.
The return statements inside that callback are only for flow of control. They just cause code to stop executing in the callback. People tend to forget that reject() and resolve() are just function calls. They don't cause the rest of the function to stop executing.
The code you show could have been written like this with only the one return statement to return the promise:
emit(event, data) {
return new Promise((resolve, reject) => {
if (!this.socket) {
reject('No socket connection.');
} else {
this.socket.emit(event, data, (response) => {
if (response.error) {
console.error(response.error);
reject(response.error);
} else {
resolve();
}
});
}
});
}
Here, it uses if/else to control program flow rather than using return to stop execution of the function early. Either can work - personal preference.
If that's the case, what does the return do in the callback function?
It just stops further code from executing inside the callback function. Just a control flow thing. There is no meaningful value to return from the callback.
what should response be?
response will be whatever the server sent as part of the acknowledgement.
what should this.socket.emit return?
this.socket.emit() happens to return the socket itself (allows for method chaining). But, that's not relevant here because there's no point in returning any value from a Promise executor function. If you do return a value, it is not used by the Promise in any way. Basically, there's too many return statements in that original code. They lead one to think the return value is relevant when it isn't. They do no harm, but do cloud up the proper intent and meaning of the code.
and how can the server resolve a promise on client side?
The server doesn't actually resolve a promise on the client side. The client defines a promise that will resolve when it gets a response back from the server. So, when that response comes back from the server, the client's own callback code that receives that response, then resolves its own promise.
What I don't get still is the callback function. So the client sends data and a callback function A to the server, and the server can call this function A in it's own callback. Function A will NOT be executed on client side, is this understanding correct? If so, then why is there resolve and reject in callback function A?
No. That's not what's happening. The client specifies a callback function to socket.io when it calls the .emit(). That tells the client socket.io library that the client would LIKE to get an acknowledgement for this message. That causes the socket.io library to set an extra bit in the message packet it sends to the server to TELL the server that the client wants an acknowledgement. No callback is sent to the server, only a request for acknowledgement bit. Then, when the server receives the message, it can call a server-side socket.io callback to say "yes, I acknowledge that I got this message" and it can also send a response. When the server calls that socket.io callback, socket.io will send an acknowledgement packet (with a matching message ID embedded in it) to the client. The socket.io client library will see the incoming acknowledgement packet, find the message ID in that packet and any server response data, find the client-side acknowledgement callback that goes with message ID and call it. The client will see its function getting called which tells it that the server did receive its message. Then, in your client code, that callback will resolve the promise.
I call http.request().end() and I need to pass a variable to the callback function.
How do I do this?
For my situation, a user accesses the server, the server sends an HTTP request to our API, and then the server needs to respond to that user with information returned from the HTTP request. I can't figure out a way to do that without using a global variable, which would be an issue, since if 2 people were to do this at once, both messages would be sent to the same user
Not sure how it would help but here's kind what I'm trying to do..
callback = function(response){
//...
respondToUser(userId);
}
function doStuff(userId){
http.request({host:'www.google.com'}, callback).end();
}
It is called a "closure" where you are keeping the context with the function you are writing, like this
function doStuff(userId){
var callback = function(response){
//...
respondToUser(userId);
}
http.request({host:'www.google.com'}, callback).end();
}
I'm facing a weird problem with SocketIO, on the server-side i emit() this :
$s.sockets.emit(scope, {some: datas},
function(feedback) {
console.log('received callback');
}
);
And on the client-side I do this :
this.Socket.on(handler, function (datas, callback) {
callback('test');
}
I'm waiting for the client to send a callback('test') the server will receive. Then it would output a simple console.log('received callback')
The client effectively receive the server request but on the client-side the callback type is null (object) instead of function ... If I remove the function() on the server side and don't wait for a callback the type on the client-side will be undefined which means that when the server expect a callback the function is somehow transmitted but transformed in an object (?)
Is it a bug ? Am I doing something wrong here ? Really weird ...
One more detail : i'm currently using SocketIO 0.9.16
Thanks people ;)
Ok I didn't find any solution on getting a callback through emit() functionality. So I made a "trick" which is a parallel working solution :
When I emit() something that will involve a callback() on the server-side, I generate a key through a function and send the datas adding a { callback : key } at the end of my datas object.
I also generate a socket.on(key, function(datas) { callback(datas); socket.removeListener(key); } which will be an equivalent of a classic callback in the system at the end.
This will be understood by the front which will emit(key, datas...) in response. The server will receive everything, do the stuff, and remove the listener.
Works like a charm but a bit complicated to make at first. Would've been nice if it worked directly with SocketIO ...
If someone got stuck, I hope this helps :)
How to check that message sent with socket.io library has been received to the client.
Is there special method for it in socket.io?
Thanks for your answers!
You should use the callback parameter while defining the event handler.
A typical implementation would be as follows:
Client side
var socket = io.connect('http://localhost');
socket.emit('set', 'is_it_ok', function (response) {
console.log(response);
});
Server side
io.sockets.on('connection', function (socket) {
socket.on('set', function (status, callback) {
console.log(status);
callback('ok');
});
});
Now check the console on the server side. It should display 'is_it_ok'. Next check console on client side. It should display 'ok'. That's the confirmation message.
Update
A socket.io connection is essentially persistent. The following in-built functions let you take action based on the state of the connection.
socket.on('disconnect', function() {} ); // wait for reconnect
socket.on('reconnect', function() {} ); // connection restored
socket.on('reconnecting', function(nextRetry) {} ); //trying to reconnect
socket.on('reconnect_failed', function() { console.log("Reconnect failed"); });
Using the callback option shown above is effectively a combination of the following two steps:
socket.emit('callback', 'ok') // happens immediately
and on the client side
socket.on('callback', function(data) {
console.log(data);
});
So you don't need to use a timer. The callback runs immediately except if the connection has any of the following states - 'disconnect', 'reconnecting', 'reconnect_failed'.
You can use the socket.io's acknowledgements.
Quote from the socket.io documentation:
Sometimes, you might want to get a callback when the client confirmed
the message reception.
To do this, simply pass a function as the last parameter of .send or
.emit. What's more, when you use .emit, the acknowledgement is
done by you, which means you can also pass data along.
On the client side simply emit the event with your data, the function will be called whenever the server responds to your event:
client.emit("someEvent", {property:value}, function (data) {
if (data.error)
console.log('Something went wrong on the server');
if (data.ok)
console.log('Event was processed successfully');
});
On the server side you get called with the data and the callback handle to send the response:
socket.on('someEvent', function (data, callback) {
// do some work with the data
if (err) {
callback({error:'someErrorCode', msg:'Some message'});
return;
}
callback({ok:true});
});
When you add a function as the last parameter of .send() or .emit() method calls, this function is called when the other party receives the message.
socket.send('hi', function() {
// if we are here, our salutation has been received by the other party.
});