How to synchronize a method in javascript? - javascript

I am trying to synchronise a singleton.
I would need to make this method like the equivalent of synchronized in java.
What happens to me is that because the socket takes a while, if the first two requests are very close together I get two websockets created. (Then from the third one onwards it takes the instance correctly).
import io from 'socket.io-client';
export default class SocketIo {
static socket = null;
static instance = null;
async initialize() {
this.socket = await io(`http://${ip}:10300/`, {
transports: ['websocket'],
});
}
static async getInstance() {
logger.info('socketIo.api.getInstance: BEGIN');
if (!this.instance) {
logger.info('socketIo.api.getInstance: creating new socket instance...');
try {
const o = new SocketIo();
await o.initialize();
this.instance = o;
logger.info('socketIo.api.getInstance: socket instance created SUCCESSFULLY');
} catch (e) {
moaLog('socketIo.api.getInstance: ERROR: ', e);
throw e;
}
} else {
logger.info('socketIo.api.getInstance: a socket instance already exists, reusing that one');
}
logger.info('socketIo.api.getInstance: END');
return this.instance;
}
}
in main.js
var socket1 = SocketIo.getInstance();
var socket2 = SocketIo.getInstance();
// ... after a while
var socket3 = SocketIo.getInstance();
2022-06-16T17:53:40.658Z: socketIo.api.getInstance: BEGIN
2022-06-16T17:53:40.660Z: socketIo.api.getInstance: creating new socket instance...
2022-06-16T17:53:41.140Z: socketIo.api.getInstance: BEGIN
2022-06-16T17:53:41.141Z: socketIo.api.getInstance: creating new socket instance...
2022-06-16T17:53:41.379Z: socketIo.api.getInstance: socket instance created SUCCESSFULLY
2022-06-16T17:53:41.382Z: socketIo.api.getInstance: END
2022-06-16T17:53:41.411Z: socketIo.api.getInstance: socket instance created SUCCESSFULLY
2022-06-16T17:53:41.415Z: socketIo.api.getInstance: END
...
2022-06-16T17:56:13.076Z: socketIo.api.getInstance: BEGIN
2022-06-16T17:56:13.078Z: socketIo.api.getInstance: a socket instance already exists, reusing that one
2022-06-16T17:56:13.079Z: socketIo.api.getInstance: END
And from server view I see two websocket connections.
Any ideas?

Below is an attempt to synchronize your singleton. The idea is to store the o.intitialize promise and check if it already has been acquired before.
I added an uid, a random value set in initialize to show that only single instance is created.
class SocketIo {
static instance = null;
static _lock = null;
async initialize() {
this.uid = Math.random();
}
static async getInstance() {
console.log('socketIo.api.getInstance: BEGIN');
if (!this.instance) {
console.log('socketIo.api.getInstance: creating new socket instance...');
const o = new SocketIo();
if (!this._lock) {
this._lock = o.initialize();
await this._lock;
this.instance = o;
console.log('socketIo.api.getInstance: socket instance created SUCCESSFULLY');
this._lock = null;
} else {
await this._lock;
}
}
console.log('socketIo.api.getInstance: END');
return this.instance;
}
}
async function Main() {
var socket1 = SocketIo.getInstance();
var socket2 = SocketIo.getInstance();
console.log((await socket1).uid);
console.log((await socket2).uid);
}
Main()
Compare it to your version:
class SocketIo {
static instance = null;
async initialize() {
this.uid = Math.random();
}
static async getInstance() {
console.log('socketIo.api.getInstance: BEGIN');
if (!this.instance) {
console.log('socketIo.api.getInstance: creating new socket instance...');
const o = new SocketIo();
await o.initialize();
this.instance = o;
console.log('socketIo.api.getInstance: socket instance created SUCCESSFULLY');
}
console.log('socketIo.api.getInstance: END');
return this.instance;
}
}
async function Main() {
var socket1 = SocketIo.getInstance();
var socket2 = SocketIo.getInstance();
console.log((await socket1).uid);
console.log((await socket2).uid);
}
Main()
Hope this is what suits you.

I solved using async-lock.
import AsyncLock from 'async-lock';
const lock = new AsyncLock();
export default class SocketIo {
// ...
static async getInstance() {
logger.info('socketIo.api.getInstance: BEGIN');
if (!this.instance) {
logger.info('socketIo.api.getInstance: creating new socket instance...');
try {
await lock.acquire('socketIo', async () => {
if (!this.instance) {
const o = new SocketIo();
await o.initialize();
this.instance = o;
logger.info('socketIo.api.getInstance: socket instance created SUCCESSFULLY');
}
});
} catch (e) {
moaLog('socketIo.api.getInstance: ERROR: ', e);
throw e;
}
} else {
logger.info('socketIo.api.getInstance: a socket instance already exists, reusing that one');
}
logger.info('socketIo.api.getInstance: END');
return this.instance;
}
}

Related

Why cannot read instance when unit testing?

*This app is for practice only.
I am trying to create multiple classes here that make up a chat room.
I create app instance as singleton instance.
App instance basically work as single module where it stores all users and rooms of the chat app.
This is what app instance looks like.
class App {
constructor() {
this.users = [];
this.rooms = [];
}
addUser(user) {
const duplicateUser = this.users.find(
(appUser) => appUser.userName === user.userName
);
if (duplicateUser) {
throw new Error("User already exists.");
}
this.users.push(user);
}
createUser(userName) {
const newUser = new User(userName);
this.addUser(newUser);
return newUser;
}
getUser(userName) {
return this.users.find((user) => user === userName);
}
....
}
const app = new App();
// Object.freeze(app); Commented out for testing purpose.
class User {
constructor(userName) {
this.userName = userName;
this.joinedRoomName = null;
}
createRoom(roomName) {
const duplicateRoom = app.getRoom(roomName);
const duplicateUser = app.getUser(this.userName);
if (duplicateRoom) {
throw new Error("The room already exists.");
}
if (duplicateUser) {
throw new Error("The user is already in another room");
}
const host = new Host(this.userName);
const room = new Room(roomName, host);
app.addRoom(room);
return room;
}
...other methods
I added a new user instance by using createUser above in the App.
When user creates an instance called room, as defined in user method,
it uses app's method(not static, but imported the instance) "getRoom and getUser".
However I get reference error like so:
TypeError: Cannot read property 'getRoom' of undefinedJest
Below is my jest code.
What is the issue here?
beforeEach(() => app.reset());
describe("클래스 인스턴스 생성 테스트", () => {
it("app 은 App 의 인스턴스여야 합니다.", () => {
expect(app).toBeInstanceOf(App);
});
it("App 에서 새로운 유저를 만들었을 때 앱에 해당 유저가 등록되어야 합니다.", () => {
const testUser = app.createUser("test");
expect(testUser.userName).toEqual(app.users[0].userName);
});
});
describe("방 개설과 방 참여 테스트", () => {
it("방을 개설했을 때 방은 App 에 추가되어야 합니다.", () => {
app.createUser("test");
const testUser = app.users[0];
const testRoom = testUser.createRoom("testRoom");
expect(testRoom.roomName).toEqual(app.rooms[0].roomName);
});
it("방 개설을 했을 때 그 방의 호스트는 개설한 유저 자신이어야 합니다.", () => {});
it("방 참여를 한다면 앱의 방에 해당 유저가 추가되어 있어야 합니다.", () => {});
it("다른 방에 속해있는 유저는 새로운 방을 개설할 수 없습니다.", () => {});
it("다른 방에 속해있는 유저는 새로운 방에 참여할 수 없습니다.", () => {});
it("사용자는 대화방을 나갈 수 있어야 합니다.", () => {});
});

Why does a unit test with sinon createStubInstance is successful but hangs?

I'm unit testing a method in fileA.js that requires a new class instance from fileB.js with success.
The problem is that I get success, as expected but this specific test hangs and I don't get results from istanbul.
Yes, I have already added --exit flag. This only happens if I run this test... Why does it hang?
This is the method in fileA.js:
global.mapperObj = {};
const opts = config.opts
(...)
async function startServices() {
const data = await getData();
Object.keys(data).forEach(function(key) {
mapperObj[key] = new ClassInFileB(key, data[key], opts);
mapperArray.push(key);
});
}
The class in fileB.js:
const npmPool = require('npmPackage');
class ClassInFileB{
constructor(ip, port, opts) {
this.classPool = npmPool.createPool(ip, port, opts);
}
(...)
// other class methods
}
And the test for that method:
const rewire = require('rewire');
const fileA = rewire(`path/to/fileA`);
const ClassInFileB = require(`path/to/fileB`);
describe('Testing startServices() function', function () {
it('It should not throw errors', async function () {
let result;
let error = false;
global.mapperArray = [];
try {
function getDataMock() {
return { '0.0.0.1': 'thisIsSomething'};
}
fileA.__set__('getData', getDataMock);
// Create a stub instance to ClassInFileB class avoiding the constructor
sinon.createStubInstance(ClassInFileB);
result = await fileA.startServices();
} catch (err) {
error = err;
}
expect(error).to.be.false;
});

not getting the object properties of a node object

so, i am very new to node and express, i ran through a problem like
here i am importing all other js files like
import { User } from './user.js';
class App {
constructor() {
this.init();
}
async init() {
this.user = await new User();
this.team = await new Team();
this.navbar = new Navbar();
this.tree = new Tree();
this.settings = await new Settings();
this.board = await new Board();
this.ping();
return this;
}
ping() {
//some functionality
}
}
now creating the object here
app = await new App();
console.log('app', app);
this gives me
app > App {}
on clicking the > i am getting this
>user: User {username: "someusername", roles: Array(1), settings: undefined}
>navbar: Navbar {timerIsRunning: false}
how can i access the properties like app.user also JSON.stringify gives me blank {}
Replace constructor in App class by next:
constructor() {
return (async () => {
return await this.init();
})();
}
now creating the object here
(async () => {
const app = await new App();
console.log("app", app);
})();

proper errors handling and cross references

The code below returns 'goN is not a function' error.How to make proper errors handler, when in case of an error we need to delete an old object and create a new one instead?
The main module udpSocket.js:
const udp = require('dgram');
const goN = require('./goNext').goNext;
class udpSocket {
constructor(config){
this.config = config;
this.socket = udp.createSocket('udp4');
this.socket.on('message', (buf) => {
this.socket.send(buf, this.config.outPort);
});
this.socket.on('error', (err) => {
console.log(err);
goN(this.socket, this.config.host);
});
}
start(){
this.socket.bind(this.config.port, this.config.host, (err) => {
if (err){
console.error(err);
goN(this.socket, this.config.host);
} else {
this.socket.send('test', this.config.outPort, this.config.host, (err) => {
if (err) {
console.error(err);
goN(this.socket, this.config.host);
} else {
console.log('UDP server up and running on '+this.config.port+' inPort, '+this.config.outPort+' outPort');
}
});
}
});
}
close(){
this.socket.close( () => {
ports.add({"in": this.config.port, "out": this.config.outPort});
delete this.socket;
});
}
}
module.exports = udpSocket;
goNext.js:
const udpSocket = require('./udpSocket');
module.exports.goNext = (socket, host) => {
if (socket != null){ delete socket; }
if (ports.length > 0){
let pp = ports.shift();
let server = new udpSocket({
port: pp.in,
outPort: pp.out,
host: host
});
sockets.set(pp.in, server);
server.start();
} else {
console.log('no sockets left');
process.exit(1);
}
}
wrapper.js
const config = require('./config').udp;
const goNext = require('./lib/goNext').goNext;
const List = require('collections/list');
global.ports = new List(config.ports);
global.sockets = new Map();
goNext(null, config.host);
goNext(null, config.host);
Maybe it's because the files require each other?
"When we require the references to another file before setting module.exports, what we are actually getting back is an empty object, not the populated object we expect"

javascript classes call function

I am beginner in programming and I am trying the javascript classes, I want to call the boardCastinit function from the override function onConnMessage, but I am getting this error message, Please help in this issue.
boReferenceError: boardCastInit is not defined
websocket.js
class websocket extends webSocketModel {
constructor() {
let server = new Server();
let mongodb = new mongoDB();
super(server.server);
}
onConnMessage(message) {
let clients = this.clients;
boardCastInit(1);
}
boardCastInit(data){
console.log(data)
}
}
module.exports = websocket;
websocketModel.js
const ws = require('websocket').server;
class webSocketModel {
constructor(httpServer) {
if(!httpServer) throw 'Null Http Server';
this.websocket = new ws({ httpServer: httpServer, autoAcceptConnections: false });
this.websocket.on('request', this.onConnOpen.bind(this));
}
onConnOpen(request) {
var connection = request.accept('echo-protocol', request.origin);
console.log('Connection Accepted');
connection.on('message', this.onConnMessage);
connection.on('close', this.onConnClose);
}
onConnMessage(message) {
if (message.type === 'utf8') {
console.log(message.utf8Data);
} else if (message.type == 'binary') {
console.log(message.binaryData.length + 'bytes');
}
}
onConnClose(reasonCode, description) {
console.log('Connection Closed');
}
}
module.exports = webSocketModel;
just change boardCastInit(1) to this.boardCastInit(1)
onConnMessage(message) {
let clients = this.clients;
this.boardCastInit(1);
}
You should be calling it from the class this reference:
class websocket extends webSocketModel {
constructor() {
let server = new Server();
let mongodb = new mongoDB();
super(server.server);
}
onConnMessage(message) {
let clients = this.clients;
this.boardCastInit(1);
}
boardCastInit(data){
console.log(data)
}
}
module.exports = websocket;
You are missing this (should be this.boardCastInit(1)).
This could be a binding issue. Perhaps you want to use arrow function instead on your onConnMessage method:
onConnMessage = (message) => {
let clients = this.clients;
this.boardCastInit(1);
}
This will ensure that this refers to the websocket class which has the boardCastInit method defined.
Try binding the boardCastInit() function inside the constructor like this.
constructor() {
let server = new Server();
let mongodb = new mongoDB();
super(server.server);
this.boardCastInit = this.boardCastInit.bind(this);
}
Then call it from the this reference.
onConnMessage(message) {
let clients = this.clients;
this.boardCastInit(1);
}

Categories

Resources