How to implement Command Pattern with async/await in JS - javascript

I'm currently implementing a WebSocket connection and I'm using a command pattern approach to emit some messages according to the command that users execute.
This is an abstraction of my implementation:
let socketInstance;
const globalName = 'ws'
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand
};
commandsQueue.forEach(command => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
socketInstance.send(message);
}
function create([url]) {
socketInstance = new WebSocket(url);
}
In order to start sending messages, the user should be run:
window.ws.push('create', 'ws://url:port');
window.ws.push('send', 'This is a message');
The problem that I have is the connection is async, and I need to wait until the connection is done to continue to the next command. Is it a good idea to implement an async/await in commandsQueue.forEach or an iterator is a better approach? What other best approaches do you recommend?

The solution that I'm using right now is: I created an empty array of messages at the beginning and then every time that I call the send command I verify if the connection wasn't opened and I added to this array.
Something like that:
const messages = [];
let socketInstance;
let isConnectionOpen = false;
const globalName = "ws";
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand,
};
commandsQueue.forEach((command) => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send,
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
if (isConnectionOpen) {
socketInstance.send(message);
} else {
messages.push(message);
}
}
function onOpen() {
isConnectionOpen = true;
messages.forEach((m) => {
send([m]);
});
messages.length = 0;
}
function create([url]) {
socketInstance = new WebSocket(url);
socketInstance.onopen = onOpen;
}

Related

command in rust does not get called via invoke, no error message

I've got 3 commands i am calling from the front end, 2 of them work perfectly, the third does not.
The issue lies with the function tournament_search
main.rs:
fn main() {
tauri::Builder::default()
.manage(ApiKey {key: Default::default()})
.invoke_handler(tauri::generate_handler![set_api_key, check_connection, tournament_search])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn set_api_key(key: String , state: State<ApiKey>){
let mut api_key = state.key.lock().unwrap();
*api_key = key;
}
#[tauri::command]
async fn check_connection(api_key: State<'_, ApiKey>) -> Result<bool, ()> {
let key = api_key.key.lock().unwrap().clone();
let res = Client::new().get(API_URL).bearer_auth(key).send().await.unwrap().text().await.unwrap();
let json: Value = serde_json::from_str(res.as_str()).unwrap();
match json["success"].as_bool() {
Some(_x) => Ok(false),
None => Ok(true)
}
}
#[tauri::command]
async fn tournament_search(search_string: String, api_key: State<'_, ApiKey>) -> Result<&str, ()> {
println!("test: {}", search_string);
let key = api_key.key.lock().unwrap().clone();
let mut query: String = String::new();
query.push_str("query($name:String){tournaments(query:{filter:{name:$name}}){nodes{name,slug,id}}},{$name:");
query.push_str(search_string.as_str());
query.push_str("}");
let res = Client::new().get(API_URL).bearer_auth(key).body(query).send().await.unwrap().text().await.unwrap();
println!("{}", res);
Ok("")
}
index.js:
const { invoke } = window.__TAURI__.tauri
window.addEventListener("load", (ev) => {
let test = document.getElementById("test");
let apiKey = document.getElementById("apiKey");
let tournamentSearch = document.getElementById("tournamentSearch");
let tourneyList = document.getElementById("tourneyList");
apiKey.addEventListener("input", (ev) => {
invoke("set_api_key", {key: apiKey.value});
invoke("check_connection").then((res) => {
if(res){
tournamentSearch.disabled = false;
}else{
tournamentSearch.disabled = true;
}
});
});
tournamentSearch.addEventListener("input", (ev) => {
test.innerText = "e";
invoke('tournament_search', {search_string: tournamentSearch.value}).then((res) => {
test.innerText = res;
});
});
});
Already looked for zero width characters, whether the event get's called in js etc. The issue is just that the function is not called.
You'd only see an error message by adding a .catch() to the invoke call.
Anyawy, the issue here is that Tauri converts command arguments to camelCase on the rust side (to match the JS default) so it would be { searchString: tournamentSearch.value } instead.
If you'd prefer snake_case instead, you can tell Tauri to use that for arguments by changing the command like this:
#[tauri::command(rename_all = "snake_case")]

How to convert Webpack 4 plugin to Webpack 5

How do I convert this plugin that worked on Webpack 4 to Webpack 5?
More specifically, the plugin() function no longer works. How do I replace this to support Webpack 5?
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
const NullFactory = require('webpack/lib/NullFactory');
class StaticAssetPlugin {
constructor(localization, options, failOnMissing) {
this.options = options || {};
this.localization = localization;
this.functionName = this.options.functionName || '__';
this.failOnMissing = !!this.options.failOnMissing;
this.hideMessage = this.options.hideMessage || false;
}
apply(compiler) {
const { localization } = this;
const name = this.functionName;
compiler.plugin('compilation', (compilation, params) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
});
compiler.plugin('compilation', (compilation, data) => {
data.normalModuleFactory.plugin('parser', (parser, options) => {
// should use function here instead of arrow function due to save the Tapable's context
parser.plugin(`call ${name}`, function staticAssetPlugin(expr) {
let param;
let defaultValue;
switch (expr.arguments.length) {
case 1:
param = this.evaluateExpression(expr.arguments[0]);
if (!param.isString()) return;
defaultValue = param = param.string;
break;
default:
return;
}
let result = localization(param);
const dep = new ConstDependency(JSON.stringify(result), expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
});
});
}
}
module.exports = StaticAssetPlugin;
Are there any migration guides for plugin creation that I can follow? Any help would be greatly appreciated.
Thanks.
You can find suitable environment details needed to run the plugin here.
Along with this, you must care about how to access event hooks
compiler.hooks.someHook.tap('MyPlugin', (params) => {
/* ... */
});
You can get more about it here
Converting your existing plugin to Webpack 5, you can tap a specific event hook and get it done.
If you try to run the plugin with the above code with Webpack 5, you will get the below error.
Many articles will suggest you update webpack-cli which is not enough.
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
const NullFactory = require('webpack/lib/NullFactory');
const PLUGIN_NAME = 'StaticAssetPlugin';
class StaticAssetPlugin {
constructor(localization, options, failOnMissing) {
this.options = options || {};
this.localization = localization;
this.functionName = this.options.functionName || '__';
this.failOnMissing = !!this.options.failOnMissing;
this.hideMessage = this.options.hideMessage || false;
}
apply(compiler) {
const { localization } = this;
const name = this.functionName;
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, params) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
});
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, data) => {
data.normalModuleFactory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, (parser, options) => {
parser.hooks.expression.for('this').tap(PLUGIN_NAME, function staticAssetPlugin(expr) {
let param;
let defaultValue;
switch (expr.arguments.length) {
case 1:
param = this.evaluateExpression(expr.arguments[0]);
if (!param.isString()) return;
defaultValue = param = param.string;
break;
default:
return;
}
let result = localization(param);
const dep = new ConstDependency(JSON.stringify(result), expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
})
});
}
}
module.exports = StaticAssetPlugin;
Importantly you have to decide which event hook you need to access from compiler and parser. You will get a list of popular hooks here, for the compiler for parser.
You can get a complete list of hooks just by accessing hooks.
for compiler
console.log(compiler.hooks);
for parser
console.log(parser.hooks);
You can choose accordingly.

Peer to peer connection not working outside local network

I am learning about webRTC and am trying to create a really simple chat app for multiple peers. Everything works great when using devices in the same network, but when I try to access my site on mobile using 4g it doesn't seem to connect (or atleast send messages). I added a google stun server to my config, but that didn't solve the problem. Does anyone see what could possibly cause my trouble? I am not receiving any errors in chrome, but firefox does tell me: WebRTC: ICE failed, add a TURN server and see about:webrtc for more details.
class socket
{
constructor()
{
this.socket = new WebSocket(`${window.location.protocol == "https:" ? "wss" : "ws"}://${window.location.host}`);
this.socket.onmessage = e => this.messageHandler(e.data);
}
async messageHandler(data)
{
data = JSON.parse(data);
let channelName = data.sender_channel_name;
switch (data.type)
{
case "sendOffer":
peerConnections[channelName] = new peerConnection();
const offer = await peerConnections[channelName].createOffer();
this.socket.send(JSON.stringify({type: 'sendOffer', sdp: offer, 'sender_channel_name':channelName}));
break;
case "offer":
peerConnections[channelName] = new peerConnection();
let answer = await peerConnections[channelName].sendAnswer(data.sdp);
this.socket.send(JSON.stringify({'type':'offer', 'sender_channel_name':channelName, 'anwser':JSON.stringify(answer)}))
break;
case "answer":
peerConnections[channelName].setAnswer(data.answer);
break;
}
}
}
class peerConnection
{
constructor ()
{
let peerConnectionConfig = {
iceServers:[
{urls:["stun:stun.l.google.com:19302"]}
]};
this.pc = new RTCPeerConnection(peerConnectionConfig);
this.pc.ondatachannel = e => {
this.dc = e.channel;
this.dc.onmessage = e => this.messageCallback(e.data);
}
this.pc.addEventListener("iceconnectionstatechange", (e) => ((pc) => {
if(pc.pc.iceConnectionState == "disconnected") {
delete peerConnections[Object.keys(peerConnections).find(key => peerConnections[key] === pc)];
}
})(this), false);
}
waitToCompleteIceGathering() {
return new Promise(resolve => {
this.pc.addEventListener('icegatheringstatechange', e => (e.target.iceGatheringState === 'complete') && resolve(this.pc.localDescription));
});
}
async createOffer()
{
this.dc = this.pc.createDataChannel("channel");
this.dc.onmessage = e => this.messageCallback(e.data);
this.pc.createOffer().then( o => this.pc.setLocalDescription(o) )
const offer = await this.waitToCompleteIceGathering();
return JSON.stringify(offer);
}
async sendAnswer (sdp)
{
this.pc.setRemoteDescription(JSON.parse(sdp));
this.pc.createAnswer().then(a => this.pc.setLocalDescription(a));
const answer = await this.waitToCompleteIceGathering();
return answer;
}
setAnswer (sdp)
{
this.pc.setRemoteDescription(JSON.parse(sdp));
}
messageCallback (data)
{
data = JSON.parse(data);
var para = document.createElement("p");
var node = document.createTextNode(data.message);
para.appendChild(node);
document.getElementById('mailbox').appendChild(para);
}
}
window.sendData = (value) =>
{
for (const connection in peerConnections)
{
try
{
peerConnections[connection].dc.send(JSON.stringify({"message":value}));
}
catch (DOMException)
{
// This when someone refreshes page, but 5 seconds to update didn't pass yet.
// Not really a problem.
}
}
}
let s = new socket();```

Wait for queue to be full in RabbitMQ using Promise

I have a piece of code where I consume events from RabbitMQ and save the events(3 types of events A,B,C) into 2 different databases A,B. I can push into Database A without any problem but I need to wait for no of events to be at least 100 to push events of type B & C into database or until the code is trying to fill up the queue for last 5 minutes from the point saveToDb in invoked. I am not able to figure out how to wait for events for B and C and then save data in database.
Note that Event A will go into Database A and Event B,C will go into Database B.
I have written following piece of code.
import { Channel, ConsumeMessage } from 'amqplib';
const BATCH_SIZE = 100;
var eventBQueue = [];
var eventAQueue = [];
const shiftElements = (message) => {
if ( message.length >= BATCH_SIZE) {
const batch= message.splice(0, BATCH_SIZE);
return batch;
}
return message;
}
const saveToDb = async (messages, database) => {
const eventsA = filterEventsA(messages);
const eventsB = filterEventsB(messages);
const eventsC = filterEventsC(eventsB);
const promises = [];
promises.push(databaseAsync.publish(eventsC));
if (eventBQueue.length < BATCH_SIZE) {
eventBQueue.push.apply(eventBQueue, eventsB);
}
else {
var eventsBBatched = shiftElements(eventBQueue);
promises.push(database.publish(eventsBBatched, EVENTS_TABLE_A));
}
if (eventAQueue.length < BATCH_SIZE) {
eventAQueue.push.apply(eventAQueue, eventsA);
}
else {
var eventsABatched = shiftElements(eventAQueue);
promises.push(database.publish(eventsABatched, EVENTS_TABLE_B));
}
return new Promise((resolve, reject) => {
Promise.all(promises).then(resolve).catch(reject);
});
}
export const process = async (database,
rabbitmq): Promise<void> => {
return new Promise((resolve, _) => {
rabbitmq.consume(async (channel, message: ConsumeMessage) => {
const messages = somefunction(message);
await saveToDb(messages,database)
.then(_ => {
try {
channel.ack(message)
} catch (error) {
}
})
.catch((error) => {
try {
console.error('error');
channel.ack(message)
} catch (error) {
}
});
});
somefunction(resolve)
});
}
Now I want to add some condition in if where no of events < Batch_SIZE to wait for data from rabbitMQ and to save in database when eventAQueue and eventBQueue has adequate size or there is a time limit waiting for this data. But I am not sure how to add it.

how to unsubscribe of function in method?

Working on dialog component with angular js and now I find out that my function is subscribed and in if condition do not quit method, but continuously executing another function afterClosed() , here is example of code :
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
return;
}
}
});
dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
What is the best and optimized way to unsubscribe beforeClosed() function?
So from your description, I understand that you dont want a second subscription to happen if the condition in the first subscriber is true, right? But you subscription will happen anyway because you instantiated it in the method, the code in the subscribe() it's just a callback. So if you dont want a lot of rewriting I will suggest storing
subscriptions in variables, so you will have an access to them and can unsubscribe at any time.
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
const beforeClosed = dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
afterClosed.unsubscribe();
return;
}
}
});
const afterClosed = dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
I hope it helps! Also you can try https://www.digitalocean.com/community/tutorials/angular-takeuntil-rxjs-unsubscribe if you have to handle multiple subscriptions.

Categories