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.
Related
I'm trying to run 3 bash scripts depending on what the JSON list sent from the client specifies, then return their outputs to a JSON dict which will be sent to the client again.
This is the code of the three scripts:
marc#linux:~ $ cat 1.sh
sleep 1
echo -n "a"
marc#linux:~ $ cat 2.sh
sleep 1
echo -n "b"
marc#linux:~ $ cat 3.sh
sleep 1
echo -n "c"
If I executed them synchronously, they might stop the event loop for 3 seconds (undesirable):
const express = require("express");
const app = express();
app.use(express.json())
const cp = require("child_process");
app.get("/", (request, response) => {
console.log(request.body);
var response_data = {};
if (request.body.includes("script1")) {
response_data.value1 = cp.execFileSync("./1.sh").toString();
}
if (request.body.includes("script2")) {
response_data.value2 = cp.execFileSync("./2.sh").toString();
}
if (request.body.includes("script3")) {
response_data.value3 = cp.execFileSync("./3.sh").toString();
}
response.json(response_data);
response.status(200)
response.send()
})
app.listen(8080, () => {
console.log("ready");
})
And if I executed them asynchronously, they would return after the response is sent, where the response would be just {}
My intended flow chart is that if I send ["script1", "script3"], it should return {"value1": "a", "value3": "c"} when the 1.sh and 3.sh are done executing, and without blocking the event loop.
Example
How do I implement callbacks/promises in such scenario?
Use Promise.all() for that:
const run = (script) => new Promise((resolve, reject) => {
// spawns cp for script
// without any args and options
cp.execFile(`./${script.slice(-1)}.sh`, null, null, (res, err) => {
if (err) reject(err);
// returns res through callback
resolve(res);
});
});
const runScripts = (req) => new Promise((resolve, reject) => {
const
result = {},
promises = [];
// creates promise for each script in request
for (script of req) {
// pushes it to the required array of promises for Promise.all
promises.push(new Promise((resolve, reject) => {
// runs script
run(script).then((res) => {
// gets scripts result
// and writes it
result[`value${script.slice(-1)}`] = res;
resolve();
});
}));
};
// uses Promise.all to wait until all scripts are done
Promise.all(promises).then(() => {
// when each script is done
// finally returns final result
resolve(result);
});
});
// put req as your request.body
runScripts(req).then((res) => {
// sends final result as response
response.status(200).json(result);
}).catch((err) => {
// if something goes wrong
response.status(500).json('Something broke!');
});
I couldn't check this code because because I have Windows.
If I try to execute scripts with child_process, it's tells me that I'm trying to execute UNKNOWN, even if I used sync function in test without anything, just console.log(cp.execFileSync("./1.sh").toString());. But, it worked for you.
So try it and tell me if it works or not.
P.S. Edited for error handling.
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.
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);
I'm trying to build some test helper functions that will help me to run some hooks with Mocha to test my GraphQL queries(just that you understand the context).
I want to do the following steps each time before I run the tests:
Connect to mongoDB with mongoose
Start the server (node)
Add some test data directly into the database (via mongoose models)
// this function returns back the server instance because I need it in the 'after' hook to be able to stop it after the tests execution.
export const startServer = () => {
mongoose.Promise = global.Promise;
mongoose.connect(MONGO_URI_TEST);
return mongoose.connection.once('open', () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return { schema: executableSchema };
})
);
return app.listen(9000, () => {
console.log('Server started!');
});
});
};
// now here's the before hook where the server is started
let server;
before( done => {
server = startServer(done); // here I need to wait
// some other async code that inserts test data into mongo
const user = new User({email: test#test.com});
user.save().then(() => {
// saved successfully
done();
})
});
I'm quite new in the JS world, how do you manage to wait (without async await syntax) until all the promises from startServer function are resolved and you get back the server instance and just after start inserting data into the database? It's a dummy question but also some links with a better explanation of this concept that I have here would be appreciated.
LE: The entire question has reduced to the followings:
const createServer = () => {
const server = app.listen(port, () => {
//callback body here
});
};
How to convert the callback to a Promise and at the same time return a valid server reference such that in the end I can do this:
const someOtherFunction = () => {
createServer().then(myValidServerInstance => {
//do something with that instance
}
}
TLDR: Some of the things about JS Promises were not clear for me, like for example the returning of the result via resolve() method.
So the right answer looks like this:
export const startServer = () => {
mongoose.Promise = global.Promise;
// first mistake was the use of callbacks rather than Promise approach offered by mongoose.connect
return mongoose.connect(MONGO_URI_TEST).then(() => {
console.log('Connected to MongoLab TEST instance.');
return createServer();
},
err => {
console.log('Error connecting to MongoLab TEST instance:', err);
});
};
The second one was returning directly the result of listen() before the operation get finished. So I've moved the code that is starting the server in another method and wrap the result of listen() into a promise and resolve the promise only when the server actually started listening.
const createServer = () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return {
schema: executableSchema,
context: { headers: req.headers},
};
})
);
return new Promise((resolve, reject) => {
const server = app.listen(9000, () => {
if (server) {
console.log('Server started on port 9000');
resolve(server);
} else {
reject();
}
});
});
};
I have a problem where my MessageChannel does not send a message from a service worker to a client. Why MessageChannel blocks forever and the client never gets message?
In the sw on fetch event I create a MessageChannel then I do postMessage, however the client never gets addEventListener('message' fired why ?
For service worker I created method like that:
self.addEventListener("fetch", async (event) => {
// Exit early if we don't have access to the client.
// Eg, if it's cross-origin.
if (!event.clientId) return null;
// eslint-disable-next-line
const client = await clients.get(event.clientId);
const response = await sendMessageToClient(client, { url: event.request.url }, 5000);
})
async function sendMessageToClient(client, message, timeoutAfter) {
// Exit early if we don't get the client.
// Eg, if it closed.
if (!client) return null;
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does.
return await new Promise((resolve, reject) => {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function (event) {
if (event.data && event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the client.
// The client can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
client.postMessage(message, [messageChannel.port2]);
// Set up the timeout
setTimeout(() => {
messageChannel.port1.close();
messageChannel.port2.close();
reject('Promise timed out after ' + timeoutAfter + ' ms');
}, timeoutAfter);
});
}
On the client I expect to get message here:
// Set up a listener for messages posted from the service worker.
// The service worker is set to post a message to specific client only
// so you should see this message event fire once.
// You can force it to fire again by visiting this page in an Incognito window.
navigator.serviceWorker.addEventListener('message', function (event) {
console.log(event.data);
});
But I never get the message on client and if there is no time out it will w8 forever.
Update
what i have tried:
window.addEventListener('message', function (event) { console.log('window.addEventListener', event.data) });
window.onmessage = function (event) { console.log('window.onmessage', event.data) };
navigator.serviceWorker.addEventListener('message', function (event) { console.log('navigator.serviceWorker.addEventListener', event.data) });
navigator.serviceWorker.onmessage = function (event) { console.log('navigator.serviceWorker.onmessage', event.data) };
// navigator.serviceWorker.controller.addEventListener('message', function (event) { console.log('navigator.serviceWorker.contoller.addEventListener', event.data) });
// navigator.serviceWorker.controller.onmessage = function (event) { console.log('navigator.serviceWorker.contoller.onmessage', event.data) };
navigator.serviceWorker.ready.then(function (registration) {
console.log('navigator.serviceWorker.ready');
registration.active.addEventListener('message', function (event) { console.log('registration.active.addEventListener', event.data) });
registration.active.onmessage = function (event) { console.log('registration.active.onmessage', event.data) };
});
navigator.serviceWorker.register("sw.js")
.then(function (registration) {
// Registration was successful
console.log("ServiceWorker registration successful with scope: ", registration.scope);
})
.catch(function (error) {
console.error("Service Worker Error", error);
});
}
and the only logs I got were:
Navigated to http://localhost:3000/ navigator.serviceWorker.ready
ServiceWorker registration successful with scope:
http://localhost:3000/