socket.emit and wait until user response after that resolve promise - javascript

I am writing web app which controls hardware. I have a server communicated with the device through the serial port. Everything works except the interaction with a user. The device has registers which I repeatedly ask waiting for some values. If some values come, I emit an event to the client and confirmation box appears. The user selects resume or abort. After that client emit the response (true or false) and I would like to resolve this response in my promise function. I need to catch response from the user exactly in the function because I have a sequence of actions I need to proceed. Promise after promise. It seems that my function ends before the user answers. How to solve this problem?
this is my code on the server:
waitUserResponse(message) {
return new Promise(async (resolve) => {
const handler = function(data) {
console.log('userAnswer = ', data);
resolve(data);
return;
}
this.io.sockets.emit('alerts', message);
this.io.sockets.once('userAnswer', handler);
})
}
this is my code on the client:
componentDidMount() {
const confirmDialog = (msg) => {
return new Promise((resolve) => {
let confirmed = window.confirm(msg);
resolve(confirmed);
return;
})
}
socket.on('alerts', data => {
confirmDialog(data).then(data => {
console.log(data);
socket.emit('userAnswer', data);
});
});
}

I should use socket.id - id for my connection
io.sockets.connected[socket.id].once('userResponce', handler);

Related

fetch().then().catch(); doesn't catch `Uncaught (in promise) DOMException: The user aborted a request.`

I have an interface on an ESP32.
I am trying to have a Connected variable show up in an interface to signal the user if he is connected to said interface or not.
I am using the following method to constantly GET the configuration from the server in order update the interface.
// get json
async function get_json(api_path, options = {}) {
// const url = api_path;
// console.log(ROOT_URL);
// console.log(api_path);
const { timeout = 8000 } = options;
const controller = new AbortController();
const timeoutID = setTimeout(() => controller.abort(), timeout);
const response = await fetch(api_path, {
...options,
signal: controller.signal,
});
clearTimeout(timeoutID);
return response.json();
}
async function getSettings() {
get_json("/api/settings/get", {
timeout: 5000,
}).then((json_data) => {
// do something with the data
connected = true;
}).catch(function (error) {
// request timeout
console.log(error);
connected = false;
console.log(error.name === "AbortError");
});
}
Everything works dandy except the catch part.
Let's say the user changes the IP of the ESP. The ESP restarts and reconfigures to use the newly IP address. But the user stayed on the same page because connected is still set to true. In the console I get Uncaught (in promise) DOMException: The user aborted a request. for each request to getSettings() because I can't write a proper catch.
Basically, after the IP is changed, getSettings() tries to GET from a wrong address, so it's normal to throw some sort of error which I should catch and change the Connected variable to false and update it in the interface so that the user can go/move/navigate to the IP Address they have just inputted.
Edit:
This is how I update connected in the interface:
// check if connected to ESP
function connectedStatus() {
let conn = document.getElementById("connected");
conn.innerHTML = connected ? "Yes" : "No";
}
setInterval(connectedStatus, 500);
"Doesn't get_json() return a PROMISE?" - All async function does return promise, But your getSettings function doesn't wait for get_json to become resolved.
let delay = ms => new Promise(ok => setTimeout(ok, ms));
async function asyncFn() {
delay(2000).then(() => {
console.log("wait! 2 secs...")
});
console.log("thanks for waiting")
}
asyncFn()
As you can see "thanks for waiting" print first,
So our guess is that, getSettings indeed modify connected state but not immediately but later, But your getSettings resolve immediately, and you didn't wait for state (connected) change.
So you need to wait for any changes,
let delay = ms => new Promise(ok => setTimeout(ok, ms));
let state;
async function asyncFn() {
await delay(2000).then(() => {
state = true
console.log("wait! 2 secs...")
});
console.log("thanks for waiting")
}
asyncFn().then(() => {
console.log("state:", state)
})

How do I correctly test asynchronous stream error event handling with mocha/sinon/chai?

I am testing an async method that returns some data from a web request using the native https.request() method in NodeJS. I am using mocha, chai, and sinon with the relevant extensions for each.
The method I'm testing essentially wraps the boilerplate https.request() code provided in the NodeJS docs in a Promise and resolves when the response 'end' event is received or rejects if the request 'error' event is received. The bits relevant to discussion look like this:
async fetch(...) {
// setup
return new Promise((resolve, reject) => {
const req = https.request(url, opts, (res) => {
// handle response events
});
req.on('error', (e) => {
logger.error(e); // <-- this is what i want to verify
reject(e);
});
req.end();
});
}
As indicated in the comment, what I want to test is that if the request error event is emitted, the error gets logged correctly. This is what I'm currently doing to achieve this:
it('should log request error events', async () => {
const sut = new MyService();
const err = new Error('boom');
const req = new EventEmitter();
req.end = sinon.fake();
const res = new EventEmitter();
sinon.stub(logger, 'error');
sinon.stub(https, 'request').callsFake((url, opt, cb) => {
cb(res);
return req;
});
try {
const response = sut.fetch(...);
req.emit('error', err);
await response;
} catch() {}
logger.error.should.have.been.calledOnceWith(err);
});
This feels like a hack, but I can't figure out how to do this correctly using the normal patterns. The main problem is I need to emit the error event after the method is called but before the promise is fulfilled, and I can't see how to do that if I am returning the promise as you normally would with mocha.
I should have thought of this, but #Bergi's comment about using setTimeout() in the fake gave me an idea and I now have this working with the preferred syntax:
it('should log request error events', () => {
const sut = new MyService();
const err = new Error('boom');
const req = new EventEmitter();
req.end = sinon.fake();
const res = new EventEmitter();
sinon.stub(logger, 'error');
sinon.stub(https, 'request').callsFake((url, opt, cb) => {
cb(res);
setTimeout(() => { req.emit('error', err); });
return req;
});
return sut.fetch(...).should.eventually.be.rejectedWith(err)
.then(() => {
logger.error.should.have.been.calledOnceWith(err);
});
});
I don't like adding any delays in unit tests unless I'm specifically testing delayed functionality, so I used setTimeout() with 0 delay just to push the emit call to the end of the message queue. By moving it to the fake I was able to just use promise chaining to test the call to the logger method.

Crossdomain ServiceWorker load balancing

I'm trying to implement smth like crossdomain load balancing with ServiceWorker API.
My concept is:
After install on every request on fetch event I try to access main domain (https://example.com/)
If success I should return this to user with like event.respondWith(__response);
If failed (timed out or any other exception) I make CORS request to other server (https://balancer.com/) which returns other accessible domain (https://mirror1.example.com) and browser is redirected;
And I'm stucked on redirection step(((
So my current code is here
self.oninstall = function (event) {
event.waitUntil(self.skipWaiting());
};
self.onactivate = function (event) {
event.waitUntil(self.clients.claim());
};
self.initialUrl = false;
self.onfetch = async function (event) {
if (!self.initialUrl)
self.initialUrl = event.request.url;
if (self.initialUrl) {
event.respondWith(self.tryAccess(event))
} else {
event.respondWith(fetch(event.request));
}
};
self.tryAccess = async (event) => {
return new Promise(async (resolve, reject) => {
self.clients
.matchAll({type: 'window'})
.then(async (clients) => {
for (var i in clients) {
var _c = clients[0];
if (_c.url === event.request.url) {
try {
let __tryResponse = await fetch(event.request);
resolve(__tryResponse);
return;
} catch (e) {
let __json = await (await fetch("https://balancer.com/")).json();
return _c.navigate(__json.path).then(client => client.focus());
}
} else {
resolve();
}
}
});
});
};
Getting a reference to a WindowClient and forcibly changing its URL from inside of a fetch handler isn't the right way to redirect.
Instead, inside of your fetch handler, you can respond with a redirection response created by Response.redirect(). From the perspective of the browser, this will be treated just like any other redirection that might have originated from the server.
One thing to note is that if you initially request a subresource via a same-origin URL that results in a redirect to a cross-origin response, you might run into some issues. If your original requests are for cross-origin URLs and your potential redirects are also to cross-origin URLs, I think you'll be fine.
self.addEventListener('fetch', (event) => {
const fetchWithRedirection = async () => {
try {
// Use fetch(), caches.match(), etc. to get a response.
const response = await ...;
// Optional: also check response.ok, but that
// will always be false for opaque responses.
if (response) {
return response;
}
// If we don't have a valid response, trigger catch().
throw new Error('Unable to get a response.');
} catch (error) {
// Use whatever logic you need to get the redirection URL.
const redirectionURL = await ...;
if (redirectionURL) {
// HTTP 302 indicates a temporary redirect.
return Response.redirect(redirectionURL, 302);
}
// If we get to this point, redirection isn't possible,
// so just trigger a NetworkError.
throw error;
}
};
// You will probably want to wrap this in an if() to ensure
// that it's a request that you want to handle with redirection.
if (/* some routing criteria */) {
event.respondWith(fetchWithRedirection());
} else {
// Optionally use different response generation logic.
// Or just don't call event.respondWith(), and the
// browser will proceed without service worker involvement.
}
});

Show an alert client side when a promise is rejected server side

I'm not super familiar with promises, but I have a few that I'm implementing within my code and would really like an alert to appear from the client side when the promise is rejected.
Here is the socket I'm calling client side
socket.emit('updateExistingItem', NewItem, (boolean) => {});
NewItem is an object that contains the fields that I want to send.
This calls a promise in my inventory.js file
export const updateExistingItem = (ItemObject) => {
return new Promise(async (resolve, reject) => {
try {
const updateExistingItem = `UPDATE console.inventory_items SET Description = '${(ItemObject.Description).replace(/\'/g, "")}', Location = '${(ItemObject.Location).replace(/\'/g, "")}', ModelNumber = '${(ItemObject.ModelNumber).replace(/\'/g, "")}'`
const response = await db(updateExistingItem, `Updating Item`);
resolve(response);
} catch (e) {
console.log("ERROR inventory.updateExistingItem: " + e);
reject(e);
}
});
};
If a user on the client side puts in information in the NewItem object that doesn't cooperate with the SQL call, I need a way to alert that user that the information wasn't saved/updated. As of right now it's a little misleading to the user, because if they put in something that get's rejected, it looks like it succeeds from their end.
If anyone has solutions that would be fantastic! Thanks.
EDIT
Here is the socket in my sockets.js file
socket.on('updateExistingItem', async (ItemObject, callback) => {
try {
const results = await updateExistingItem(ItemObject);
if (results.affectedRows === 1) {
callback(true);
}
callback(false);
}
catch (error) {}
});
SocketIO being used in my server.js file
var server = http.createServer(app);
const io = socketIO(server);
io.on('connection', (socket) => {
require('./middleware/sockets')(socket);
});
There are 4 different files where these are used so this is why I only put the snippets that actually pertain to this specific call.
Ok so I was able to find a decent solution to this on my own.
In my socket within my sockets.js file I put a callback within the catch method like so
socket.on('updateExistingItem', async (ItemObject, callback) => {
try {
const results = await updateExistingItem(ItemObject);
if (results.affectedRows === 1) {
callback(true);
}
callback(false);
}
catch (error) {
callback(false);
}
});
From there I decided to console log the boolean value that is found in the socket.emit on the client side, and was able to get this callback value in return.
Now it looks something like this
socket.emit('updateExistingItem', NewItem, (boolean) => {
if(boolean){
alert('Successful!');
}else{
alert('UnSuccessful!');
}
});
I now get updates from the client side if this promise gets rejected or resolved.

Node.js "net.createConnection()" error callback not emitting

I want to connect to a Unix Domain Socket server in a Node application. If the connection succeeds and was opened, a loop (that may take some time) shall be executed. If an error occurs during the execution of this loop, it should receive some kind of notification. If a connection to the client is not possible at all, the loop should not be executed in the first place (that seems to work with the Promise). To me this sounds like the most simple thing in the world, but I just can't get it to work... This is what I have until now:
new Promise(function(resolve, reject) {
let connection = net.createConnection('/tmp/socket.s', () => {resolve(connection);})
.on('data', function(data) {
// Do something (during loop execution)
})
.on('error', reject); // If this callback is executed, the while loop should terminate (by receiving some kind of signal within the loop)
}).then(function(connection) {
for(...) {
// Do stuff that takes some time, executes other callbacks, sends data to the socket
}
connection.end();
}, function(error) {
// Error handling
});
What am I missing?
Try to listen to the data event in the resolve section of the promise. The following code should do it:
const net = require('net');
/**
* Client
* --------------------------------------------
*/
new Promise((resolve, reject) => {
let client = net.createConnection({ path: '/tmp/socket.s'}, () => {
console.log('Client: connected ')
resolve(client);
});
// Reject on error
client.on('error', err => reject(err) );
client.on('end', () => {
console.log('Client: disconnected from server #1');
});
}).then( connection => {
connection.on('data', data => {
// Do stuff with the data
console.log(`Client: the server says: ${data.toString()}\n`);
if(data != 'Data recieved'){
// Just to ensure that the following loop runs only once
for (let i = 0; i <= 10; i++) {
setTimeout(() => {
// Send data to the server
connection.write(`Client Data ${i}`);
if (i == 10) {
// Close the connection after everything is done
connection.end();
}
}, i*2000);
}
}
});
}, error => {
console.log('Client: promise rejection error', error );
});
My test server looks like this
const net = require('net');
/**
* Server
* --------------------------------------------
*/
const server = net.createServer( connectionListener => {
console.log(`#${process.pid} Server: client connected`);
connectionListener.on('end', () => {
console.log(`#${process.pid} Server: client disconnected`);
});
connectionListener.write('Hello\r\n');
connectionListener.on('data', data => {
console.log(`#${process.pid} Server: client sends: ${data.toString()}`);
connectionListener.write('Data recieved');
});
});
server.on('error', (err) => {
console.log(err);
server.close();
});
server.listen('/tmp/socket.s', () => {
console.log(`#${process.pid} Server: server bound`);
});
process.on('exit', code => {
console.log(code);
server.close();
});
process.on('SIGTERM', () => server.close() );
process.on('SIGINT', () => server.close() );
In this example the client sends data to server and the server replies each time. The client then closes the connection after having sent data 10 times.
P.S. There is no need to use a Promise unless you do need to return a promise at some point in your code.

Categories