javascript Socket io and promise - javascript

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.

Related

JS - send WS message, wait for response, then send again depending on the response (all inside one function)

My goal is to, in one function, send a ws message:
ws.send(message),then wait for server response, which is handled by ws.onmessage = async (event) => - and depending on the server answer, send next message inside the same function
The problem is that I cannot receive response from the server before leaving the function. What tool would be the best to use in this case? (Im still at begginer level in JS)
Full code of the function is:
function ComboAction(data){
ws.send('message1')
//here i want to wait for server's response and continue this function depending on the response.
//wait for server response?
ws.send('message depending on server response to first message')
}
I was wondering out with promises? or the => method, but it's out of my reach for now.
Any tips will be greatly appreciated!

How to effectively .then chain on client with .catch?

I have the following .then example chain in my React Native client code, currently without a .catch because I am looking for advice on how to set it up:
await getUserInfo(userId, JSON.stringify(ratingsQueryRatingType), 1)
.then(async userRatingData => {
await findMatchHistory(userId, '', 3)
.then(async matchHistoryData => {
These functions make calls to my NodeJS server. The NodeJS server then sends back the data.
I am trying to find out how I can effectively send back an error from the server to the client, and have the .catch part in the client handle that (e.g. with Alert.alert(error)).
I tried to throw an error on my server as follows but then on my server I get Unhandled promise rejection. It appears that it does not send the error back to client.
// Other code before this part
if (response==='Success') {
return res.status(200).json({'status': 'success'})
} else {
throw 'Match record was not confirmed successfully'
}
Or is it common practice to send response objects from the server (instead of Errors) and then handling those on the client with some kind of if-statement, such as the following?
if (results['status']==='success') {
// Code
} else if (results['status']==='failure') {
// Code
}
I do read about .then chaining with .catch being an attractive option so it feels like this would not be the correct solution..
I think we should send error-codes to the client instead of sending message although you can do it too. you can check the status code based on the error occurred on the backend -> https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
Also, on client side you can use interceptor and create an error-handler service layer and based on the code you will be sending to the client side you can handle that. you can follow these steps to setup one for your app : https://bilot.group/articles/using-react-router-inside-axios-interceptors/
And, On the backend side if there is an error try logging it to log files on your server.
// Other code before this part
if (response==='Success') {
return res.status(200).json({'status': 'success'})
} else {
res.status(based on what happened on server).json({'status': 'failure'})
}
For .then channing instead you should use async/await. for better understanding how to use them and convert chain to asyn/await. This doc contains the step-by-step guide: https://advancedweb.hu/how-to-refactor-a-promise-chain-to-async-functions/

Is there a way to send response from server even if no callback is provided from client side?

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.

GCP Nodejs8 Cloud Function - Synchronous PubSub publish

I'm struggling with a javascript/Nodejs8 Google Cloud Function to publish payloads to Google PubSub.
So I have a Cloud Function triggered by HTTP requests and the request body is then published to a pubsub topic (configured for pull mode).
Here is my code:
const {PubSub} = require('#google-cloud/pubsub');
const pubsub = new PubSub();
const topic = pubsub.topic('my-fancy-topic');
function formatPubSubMessage(reqObj){
// the body is pure text
return Buffer.from(reqObj.body);
};
exports.entryPoint = function validate(req, res) {
topic.publish(formatPubSubMessage(req)).then((messageId) => {
console.log("sent pubsub message with id :: " + messageId)
});
res.status(200).json({"res":"OK"});
};
My issue is that the cloud function finishes executing before the pubsub message being published (in logs, the log "Function execution took X ms, finished with status code: 200" shows up around 30 or 40 seconds before the my pubsub log. I also had several times a log with "Ignoring exception from a finished function" and I dont get my pubsub log)
I'm not a javascript or nodejs specialist and I don't master javascript promises neither but I was wondering if I could make the publish synchronous. I'm thinking as well that I might be doing something wrong here !
Thank you in advance for your help.
In your logic, your callback / event handler function is being called when the HTTP message arrives. You then execute a publish() function. Executing a publish is an asynchronous activity. This means that it make take some time for the publish to complete and since JavaScript (intrinsically) doesn't want to block, it returns immediately with a promise that you can then use to be notified when the asynchronous work has completed. Immediately after executing the publish() your logic executes a res.status(....) which sends a response to the HTTP request and that is indeed the end of the flow request from the HTTP client. The asynchronous publish is still cooking and when it itself completes, then the callback for the publish occurs and you log a response.
Unfortunately, this is not a good practice as documented by Google here ...
https://cloud.google.com/functions/docs/bestpractices/tips#do_not_start_background_activities
In this last story, the function you call validate will still end prior to the publish being completed. If you want to block while the publish() executes (effectively making it synchronous), you can use the JavaScript await key word. Loosely, something like:
try {
let messageId = await topic.publish(....);
console.log(...);
catch(e) {
...
}
You will also need to flag the functions as being async. For example:
exports.entryPoint = async function validate(req, res) {
...
See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
You can also simply return a Promise from the function and the callback function will not be considered resolved until the Promise as a whole is resolved.
The bottom line is to study Promises in depth.
Right now, this code is sending a response before the publish is complete. When the response is sent, the function is terminated, and ongoing async work might not complete.
What you should do instead is send the response only after the publish is complete, which means putting that line of code in the then callback.
exports.entryPoint = function validate(req, res) {
topic.publish(formatPubSubMessage(req)).then((messageId) => {
console.log("sent pubsub message with id :: " + messageId)
res.status(200).json({"res":"OK"});
});
};
I suggest spending some time learning about how promises work, as this is crucial to building functions that work correctly.

Meteor method returns undefined to the client (asynchronous)

I've been working on integrating Google Recaptcha into a Meteor and AngularJS web application. Everything was smooth sailing until I had to validate the recaptcha response -- for some bizarre reason, I can't get an async response from the backend to the frontend.
I've tried a lot of different variations and have read many, many posts on SO and the internet in general, but with no luck -- so I opted to post my own question.
Here's what I'm doing:
Client:
Meteor.call('recaptcha.methods.validateRecaptcha', { 'response' : this.recaptcha.getResponse(this.id) }, function(error, result) {
// error and result are both undefined
console.log('Do something with the ' + error + ' or ' + result + '.');
}
So, I'm calling a Meteor method and passing in a callback that is run when the method is done. However, the error and result parameters are both undefined.
Server:
run: function(data) {
if (this.isSimulation) {
/*
* Client-side simulations won't have access to any of the
* Meteor.settings.private variables, so we should just stop here.
*/
return;
}
return Meteor.wrapAsync(HTTP.post)(_someUrl, _someOptions);
}
That last line is a shortened version of the sync/async structure that I've found in several Meteor guides (I also tried this version), namely:
var syncFunc = Meteor.wrapAsync(HTTP.post);
var result = syncFunc(Meteor.settings.private.grecaptcha.verifyUrl, _options);
return result;
I've also tried a version using Futures:
var Future = Npm.require( 'fibers/future' );
var future = new Future();
var callback = future.resolver();
HTTP.post(Meteor.settings.private.grecaptcha.verifyUrl, _options, callback);
return future.wait();
Now, the intention here is that I use Meteor.call() to call this method from the client, the client-side stub runs (to prevent simulation errors since we use private Meteor.settings variables in the real non-SO server-side code) and returns immediately (which happens), and the server hits Google's Recaptcha API (which happens and the server receives a response) before returning the result to the client (which doesn't happen -- the callback occurs but with no error/success data).
My thought is that one of two things are happening:
I'm just doing something wrong and I'm not properly sending the data back to the client.
The synchronous client stub (which returns immediately) is telling the client that the server response isn't important, so it never waits for the proper asynchronous response.
Could any of the Meteor gurus weigh in here and let me know what's going on and how to get async requests to play nicely in a Meteor application?
Thanks!
From the documentation for HTTP.call, which is the generic version of HTTP.post, it says
Optional callback. If passed, the method runs asynchronously, instead of synchronously, and calls asyncCallback. On the client, this callback is required.
So, on server, you can run it asynchronously like this
run: function(data) {
if (this.isSimulation) {
/*
* Client-side simulations won't have access to any of the
* Meteor.settings.private variables, so we should just stop here.
*/
return;
}
// No need to pass callback on server.
// Since this part is not executed on client, you can do this
// Or you can use Meteor.isClient to run it asynchronously when the call is from client.
return HTTP.post(Meteor.settings.private.grecaptcha.verifyUrl, _options);
}

Categories

Resources