nodejs: event.on is async? - javascript

I am reading this post: write async function with EventEmitter
Is the following piece of code async?
var event = new events.EventEmitter();
event.on("done", cb);

In the given code in question, you are subscribing for an event. When you call event.emit("done"), node execute the given callback in the same order they are subscribed.
Example
var event = new events.EventEmitter();
event.on("done",() => {
console.log(("notified 1"))
});
event.on("done",() => {
setImmediate(() => {
console.log("async")
});
console.log(("notified 2"))
});
event.on("done",async () => {
console.log(("notified 3"))
});
console.log("before firing an event");
event.emit("done");
console.log("after firing an event");
Output
before firing an event
notified 1
notified 2
notified 3
after firing an event
async

Related

Error handling when testing Solana event emitting

I'm writing a test of event emitting in my Solana program as described here: https://github.com/coral-xyz/anchor/blob/master/tests/events/tests/events.js
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Events;
it("Is initialized!", async () => {
let listener = null;
let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
assert.isAbove(slot, 0);
assert.strictEqual(event.label, "hello");
});
It works good if the instruction completes successfully. But if any error happens during execution, the test code silently waits forever for event emitting which expectedly doesn't happen.
Can anyone please suggest a way to deal with such exceptions so that they are not "swallowed" and thrown on upper level?
If I understand the issue correctly, you need to add some sort of timeout when waiting for the event, correct? In that case, you should be able to use a setTimeout to check the result of your listener and error if it hasn't fired, ie:
it("Is initialized!", async () => {
let listener = null;
let event = {label: ""};
let slot = 0;
setTimeout(function(){
assert.isAbove(slot, 0);
assert.strictEqual(event.label, "hello");
},5000);
[event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
});

How to set up multiple firebase .on() listeners and trigger function after all have resolved

I have multiple on() listeners set up on different nodes to get data required for generating some tables of data. After a listener updates, it regenerates all the tables on the website. The problem is that on the first webpage load, it has to regenerate the tables once for each .on() listener, this is excessive. I tried making each listener into a promise so that I could run gen_tables() only after they all resolve and it worked great, except that the listeners seem to have been called off and no longer listen. How can I have the gen_tables() run only once on first load, but then still have my multiple listeners continue listening?
Here is my code that works, but has to run gen_tables() 6 times
//LISTENER - CURRENT_WEEK_DATA
db.ref(`weeks/${currentweek}`).on("value", (snap) => {
currentWeekData = snap.val();
console.log("DOWNLOADING currentWeekData From Firebase: ", currentWeekData);
gen_tables();
});
//LISTENER - ORDERS_PROCESSING
db.ref("orders_processing").on("value", (snap) => {
orders_processing = snap.val();
console.log(
"DOWNLOADING orders_processing From Firebase: ",
orders_processing
);
gen_tables();
});
//LISTENER - ORDERS_META
db.ref("orders_meta").on("value", (snap) => {
orders_meta = snap.val();
console.log("DOWNLOADING orders_meta From Firebase");
gen_tables();
});
//LISTENER - ORDERS NEXT WEEK Do i neeed this?
db.ref("orders_next_week").on("value", (snap) => {
orders_next_week = snap.val();
console.log("orders_next_week DOWNLOADED From Firebase: ", orders_next_week);
gen_tables();
});
//LISTENER - PRODUCTS
db.ref("products").on("value", (snap) => {
console.log("DOWNLOADING PRODUCTS");
products = snap.val();
gen_tables();
});
//LISTENER -PRODUCTS_META
db.ref("products_meta").on("value", (snap) => {
console.log("DOWNLOADING PRODUCTS_META");
products_meta = snap.val();
gen_tables();
});
and here is the code with the Promise.all method to wait until all .on() have resolved to run gen_tables() only once, but doesn't continue listening after first load:
const listener = (ref) => {
return new Promise((resolve, reject) => {
const onError = (error) => reject(error);
const onData = (snap) => resolve(snap.val());
db.ref(ref).on("value", onData, onError);
});
};
let listeners = [
listener("orders_meta"),
listener("orders_next_week"),
listener("orders_processing"),
listener("products"),
listener("products_meta"),
listener(`weeks/${currentweek}`),
];
Promise.all(listeners).then(function (values) {
console.log(values);
orders_meta = values[0];
orders_next_week = values[1];
orders_processing = values[2];
products = values[3];
products_meta = values[4];
currentWeekData = values[5];
gen_tables();
});
You should rethink your architecture. Since you're not passing any values to gen_tables() I'm assuming it updates all tables, is that correct? In that case why not set up a Cloud Function that's triggered by Cloud Scheduler every n minutes to execute gen_tables()?
If you want to only call gen_tables() if an update has occurred, use your .on() listeners to set a value in your database to 1 when a write happens and when your Cloud Function executes it only calls gen_tables() if that value is 1.
If you want more nuanced updates, use your .on() listeners to update a queue with the table name to update and then have a Cloud Scheduler-triggered Cloud Function check every n minutes for updates and process those.

Promisify event based pattern

How can I promisify a websocket event based communication, to work like ajax?
constructor(){
this.ws = new WebSocket(...);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// send data to doRequest
};
},
async function doRequest(data){
this.ws.send(JSON.stringify(data));
return new Promise((resolve, reject) => {
// how to wait and get data?
resolve(data);
});
}
I was thinking to add my own event emmiter and fire event inside onmessage function, then hook the promise from doRequest to that event, but seems kind of convoluted?
Websockets can receive and send data in both directions at any time, whereas Promises only resolve once with one value. Thus Promises might not be the right tool here.
If you however want to write an abstraction around the response to one request, then a Promise makes perfectly sense:
Add an event listener that listens for the message event, but only listen for it once and remove it, send the data and resolve whenever the message event has been dispatched.
Remove function as this is a method of the class you are creating.
doRequest(data){
return new Promise(resolve => {
this.ws.addEventListener('message', event => {
resolve(event.data);
}, {once: true});
this.ws.send(JSON.stringify(data));
});
}
In your constructor only create the WebSocket instance.
constructor(){
this.ws = new WebSocket(...);
}
And then use it like this:
const socket = new YourWebSocketClass();
socket.doRequest({message: 'hello'}).then(message => {
const data = JSON.parse(message);
});

Emit function on sinon mock

I have a function that I need to test using sinon. It takes two arguments and has different events that can be raised. I'm trying to simulate the 'ready' evet made to simulate a successful SFTP connection
function configureSFTPConnection(conn, connectionSettings) {
'use strict';
return new Promise(function(resolve, reject) {
conn.on('ready', function() {
resolve(conn);
}).on('error', function(err) {
reject(err);
}).connect(connectionSettings);
});
}
I can simulate the exterior connect function.
configureSftpStub = sinon.stub(clientObject, 'connect');
How can I force the ready callback to execute, completing the promise?
This is what I'm trying:
clientObject = new client();
configureSftpStub = sinon.stub(clientObject, 'connect');
configureSftpStub.onCall(0).returns(function() {
console.log('trying to do something');
resolve();
});
The .onCall() never seems to run.
What was needed was rather than trying to return something I needed to replace the function that was called and do a simple .emit call within the function.
configureSftpStub = sinon.stub(clientObject, 'connect', function() {
this.emit('ready');
});

Temporarily disable event listeners and rebind later

I have a basic websocket program. I have a data callback where it analyzes the message and emits an event. Here is a minimal example showing everything that needs to be shown.
Cylon = new EventEmitter();
Cylon._createConnection = function(name) {
let self = this;
let socket = net.connect(this.config.port, this.config.host);
socket.on('data', Meteor.bindEnvironment(function (data) {
let args = _.compact(data.toString('ascii').split(':'));
args.push(name);
self.emit.apply(self, args);
}));
return socket;
}
Cylon._commands = Cylon._createConnection('commands');
Cylon.on('TriggerActivated', (trigger) => {
console.log(`Trigger warning! ${trigger}`);
});
Here is the problem: there exists an echo command that will simply relay the information as a string on the socket. Meaning the Cylon.on('TriggerActivated') could be tricked by doing MG "TriggerActivated:test"
So my question is: is there a way to temporarily unbind all event listeners?
Cylon._sendCommand = function (command) {
check(command, String);
if (command.splice(0,2) === 'MG') {
// Unbind event listeners
Cylon._commands.on('data', console.log);
Cylon._commands.write(command);
// Rebind all previous event listeners here
}
}
Is this possible with JavaScript/node?
You can do this (not tested):
// get current listeners
var listeners = Cylon.listeners(command)
// Unbind event listeners
Cylon.removeAllListeners(command);
// do your special processing
Cylon._commands.on('data', console.log);
Cylon._commands.write(command);
// Rebind all previous event listeners here
listeners.forEach(function(listener) {
Cylon.on(command, listener);
};
But it's better to not emit the event at all:
socket.on('data', Meteor.bindEnvironment(function (data) {
if (data.splice(0,2) !== 'MG') {
let args = _.compact(data.toString('ascii').split(':'));
args.push(name);
self.emit.apply(self, args);
} else {
console.log(data);
}
}));
Or something like this (emit a special event that you only subscribe to it).

Categories

Resources