Double associative array or 1(n) loop more efficient? - javascript

I'm working on a WebSocket server using NodeJS and I really need to be able to look up a "class" by both the socket and the id of the user.
So I'm trying to figure out what would be more efficient, and why.
var usersBySocket = {};
var usersById = {}
// ID is pulled from database in NetworkClient constructor.
server.on('connection', function(client) {
var networkClient = new NetworkClient(client);
usersBySocket[client] = networkClient;
usersById[networkClient.getId()] = networkClient;
});
// When needing to send a specific user some data
// based on his user id (IE: Messaging).
// usersById(...).send(...);
OR
var users = {}
server.on('connection', function(client) {
users[client] = new NetworkClient(socket);
});
function getUserById(id) {
for(var uid in users) {
if(uid == id) return users[uid];
}
return undefined;
}
// Then when I needto use it, call it like so:
// getUserById(..).getSocket().send(..);
I'm leaning towards the first option, but I'm not sure exactly how JavaScript handles storing the values, if each "associative array" stores by value and not by reference, it's a complete waste. I especially don't want obsessive memory copy.

Objects are stored by reference. But the first option is always going to use more memory for the usersById object.
The second option uses less memory, but you are trying to do a linear search to find the user with the right id, this is not efficient.

Related

JS: Node.js and Socket.io - globals and architecture

Dear all,
Im working with JS for some weeks and now I need a bit of clarification. I have read a lot of sources and a lot of Q&A also in here and this is what I learned so far.
Everything below is in connection with Node.js and Socket.io
Use of globals in Node.js "can" be done, but is not best practice, terms: DONT DO IT!
With Sockets, everything is treated per socket call, meaning there is hardly a memory of previous call. Call gets in, and gets served, so no "kept" variables.
Ok I build up some chat example, multiple users - all get served with broadcast but no private messages for example.
Fairly simple and fairly ok. But now I am stuck in my mind and cant wrap my head around.
Lets say:
I need to act on the request
Like a request: "To all users whose name is BRIAN"
In my head I imagined:
1.
Custom object USER - defined globally on Node.js
function User(socket) {
this.Name;
this.socket = socket; }
2.
Than hold an ARRAY of these globally
users = [];
and on newConnection, create a new User, pass on its socket and store in the array for further action with
users.push(new User(socket));
3.
And on a Socket.io request that wants to contact all BRIANs do something like
for (var i = 0; i < users.length; i++) {
if(user[i].Name == "BRIAN") {
// Emit to user[i].socket
}}
But after trying and erroring, debugging, googling and reading apparently this is NOT how something like this should be done and somehow I cant find the right way to do it, or at least see / understand it. can you please help me, point me into a good direction or propose a best practice here? That would be awesome :-)
Note:
I dont want to store the data in a DB (that is next step) I want to work on the fly.
Thank you very much for your inputs
Oliver
first of all, please don't put users in a global variable, better put it in a module and require it elsewhere whenever needed. you can do it like this:
users.js
var users = {
_list : {}
};
users.create = function(data){
this._list[data.id] = data;
}
users.get = function(user_id){
return this._list[user_id];
};
users.getAll = function(){
return this._list;
};
module.exports = users;
and somewhere where in your implementation
var users = require('users');
For your problem where you want to send to all users with name "BRIAN",
i can see that you can do this good in 2 ways.
First.
When user is connected to socketio server, let the user join a socketio room using his/her name.
so it will look like this:
var custom_namespace = io.of('/custom_namespace');
custom_namespace.on('connection', function(client_socket){
//assuming here is where you send data from frontend to server
client_socket.on('user_data', function(data){
//assuming you have sent a valid object with a parameter "name", let the client socket join the room
if(data != undefined){
client_socket.join(data.name); //here is the trick
}
});
});
now, if you want to send to all people with name "BRIAN", you can achieve it by doing this
io.of('/custom_namespace').broadcast.to('BRIAN').emit('some_event',some_data);
Second.
By saving the data on the module users and filter it using lodash library
sample code
var _lodash = require('lodash');
var Users = require('users');
var all_users = Users.getAll();
var socket_ids = [];
var users_with_name_brian = _lodash.filter(all_users, { name : "BRIAN" });
users_with_name_brian.forEach(function(user){
socket_ids.push(user.name);
});
now instead of emitting it one by one per iteration, you can do it like this in socketio
io.of('/custom_namespace').broadcast.to(socket_ids).emit('some_event',some_data);
Here is the link for lodash documentation
I hope this helps.

Calculated fields in json API?

I'm wondering to which extend should we add in an API values that can be calculated from the raw data and extra available information (from the browser session, user interface.. or whatever)
For example, we could have an API returning this JSON:
{
ownder_id: '123',
canAddComment: true,
etc...
}
That gives us the value "canAddComment" directly.
Or we could have just this:
{
ownder_id: '123',
etc...
}
Where, comments can be calculated from the owner_id. For example, a user can add comments if the session owner_id is different from the received from the API.
We could have done this in Javascript instead:
//supposing we have a session object with our browser session values
var params = {owner_id: session.owner_id};
$.post(apiURL, params, function(data){
var result = JSON.parse(data);
//processing the data
result["canAddComments"] = result.owner_id !== session.owner_id;
//do whatever with it
});
What would be the best approach? What's the recommendation in this kind of cases?
I'm not exactly sure what you want to know. But my first reaction is you make a function out of it.
// constructor
function Comment() {
this.owner_id;
this.canAddComment = function() {
return session.user_id === this.owner_id;
}
}
var session = {
user_id: 22,
name: 'Kevin'
};
var my_comment = new Comment();
my_comment.owner_id = 15;
if(my_comment.canAddComment()) {
$.ajax({
// now read the form and submit the data ...
})
}
else {
alert('Only the author of this message can update ...');
}
EDIT:
My question was mainly if this should better be calculated in the backend side or calculated after retrieving the API data.
Not sure if there is a generic answer. A few arguments: if you want the method to be secret, you must do it on server side. In other cases, I think it makes perfect sense to let the client PC use its processing power.
Okay, one example: sorting. Let's say there is a table full of data; clicking on the head sorts the table according to that column. Does it make sense to send all the data to the server, let it sort the table and return an array of keys? No, I think it makes more sense to let the client process this.
http://tablesorter.com/docs/
Similar with Google Maps: you drag a marker to some place, then the program must calculate the closest 5 bus stops. Well, obviously you calculate all of this on client side.

Retain multiple fileEntries with chrome filesystem API?

How can I retain multiple fileEntry objects to reuse when the user opens the app with the chrome filesystem API? I've tried to use arrays to store the file entries and it works fine up until I restart the app and can't use them. Instead of returning a fileEntry object the console returns an object object (this is after I used chrome.storage.local.set and chrome.storage.local.get to set and retrieve the entries once the user restarted the app.)
You cannot persist the actual file entries between sessions. You need to use chrome.fileSystem.retainEntry which will return an id, which you can store, and then use chrome.fileSystem.restoreEntry to regain access to the entry when the app is re-opened.
After a lot of back and forth, I figured out how to retain multiple fileEntries.
Here's the code for the people who need it.
//save
//'fileEntries' is an array that I made earlier where multiple entries were stored
var keep = [];
fileEntries.forEach(function(entry, index) {
//if it's empty or null, return nothing
//otherwise add the id to our empty array 'keep'
if (entry == null) {
//do nothing
} else {
keep[index] = chrome.fileSystem.retainEntry(entry);
}
});
chrome.storage.local.set({
retained: keep
});
//load
chrome.storage.local.get('retained', function(data) {
var retained = data.retained;
retained = retained.filter(function(d) {
return d
});
for (var i = 0; i < retained.length; i++) {
var tab = retained[i];
chrome.fileSystem.restoreEntry(tab, function(entry) {
fileEntries.push(entry);
});
}
});
And.... thats about it! When the loading function is done, the fileEntries array, has all the fileEntry objects!

Create object and add to array of pointers - always replaces existing array

I have probably misunderstood the documentation somehow but cannot figure this out.
What I want to do, is to be able to create a new ClientContact and save it to an array of pointers called contacts in Clients table.
This is the relevant code:
var Client = Parse.Object.extend("Client");
var selectedClient = new Client();
// sets the objectId based on URL params
selectedClient.id = $routeSegment.$routeParams.id;
var ClientContact = Parse.Object.extend("ClientContact");
var contact = new ClientContact();
contact.set('name', 'test');
contact.set('desc', 'some description');
contact.set('phoneNumber', '123');
selectedClient.add('contacts', contact);
selectedClient.save().then(function() {
console.log('saved');
}, function(error) {
console.error(error);
});
As expected, the contact is automatically saved and added to contacts when the selectedClient is saved.
But if I run the same code again (in the testing this means refreshing the page), a new ClientContact is saved but it replaces the contacts array entirely.
That is, only the most recent ClientContact is associated to the Client, any new additions replaces the array, leaving only one pointer.
I hope there is an obvious an easy fix that I have simply failed to spot.
Ok seems I have located the problem.
I was assuming that array operations on an object was independant on the local representation.
What I did above was to construct an object based on a known objectId:
var Client = Parse.Object.extend("Client");
var selectedClient = new Client();
// sets the objectId based on URL params
selectedClient.id = $routeSegment.$routeParams.id;
Turns out that array operations like add and remove are performed based on the local information. So doing the above construction leaves my array, in the selectedClient object, empty at each page refresh. This was the cause of my array being replaced with a new single valued array.
In short, one should fetch an object before performing array operations it seems.
The reason for the construction above was to avoid having to re-query the Client object when navigating around an AngularJS based webpage. The solution was fortunately simple: loading the Client object once in a parent scope.
This moves outside the scope of the question. Just wanted to mention that as well.

How can I cleanly pull a Parse.Object relation's records when fetching the object?

In the Parse JavaScript guide, on the subject of Relational Data it is stated that
By default, when fetching an object, related Parse.Objects are not
fetched. These objects' values cannot be retrieved until they have
been fetched.
They also go on to state that when a relation field exists on a Parse.Object, one must use the relation's query().find() method. The example provided in the docs:
var user = Parse.User.current();
var relation = user.relation("likes");
relation.query().find({
success: function(list) {
// list contains the posts that the current user likes.
}
});
I understand how this is a good thing, in terms of SDK design, because it prevents one from potentially grabbing hundreds of related records unnecessarily. Only get the data you need at the moment.
But, in my case, I know that there will never be a time when I'll have more than say ten related records that would be fetched. And I want those records to be fetched every time, because they will be rendered in a view.
Is there a cleaner way to encapsulate this functionality by extending Parse.Object?
Have you tried using include("likes")?
I'm not as familiar with he JavaScript API as the ObjC API.. so in the example below I'm not sure if "objectId" is the actual key name you need to use...
var user = Parse.User.current();
var query = new Parse.Query(Parse.User);
query.equalTo(objectId, user.objectId);
query.include("likes")
query.find({
success: function(user) {
// Do stuff
}
});
In general, you want to think about reverse your relationship. I'm not sure it is a good idea be adding custom value to the User object. Think about creating a Like type and have it point to the user instead.
Example from Parse docs:
https://parse.com/docs/js_guide#queries-relational
var query = new Parse.Query(Comment);
// Retrieve the most recent ones
query.descending("createdAt");
// Only retrieve the last ten
query.limit(10);
// Include the post data with each comment
query.include("post");
query.find({
success: function(comments) {
// Comments now contains the last ten comments, and the "post" field
// has been populated. For example:
for (var i = 0; i < comments.length; i++) {
// This does not require a network access.
var post = comments[i].get("post");
}
}
});
Parse.Object's {Parse.Promise} fetch(options) when combined with Parse.Promise's always(callback) are the key.
We may override fetch method when extending Parse.Object to always retrieve the relation's objects.
For example, let's consider the following example, where we want to retrieve a post and its comments (let's assume this is happening inside a view that wants to render the post and its comments):
var Post = Parse.Object.extend("Post"),
postsQuery = new Parse.Query(Post),
myPost;
postsQuery.get("xWMyZ4YEGZ", {
success: function(post) {
myPost = post;
}
).then(function(post) {
post.relation("comments").query().find({
success: function(comments) {
myPost.comments = comments;
}
});
});
If we had to do this every time we wanted to get a post and its comments, it would get very repetitive and very tiresome. And, we wouldn't be DRY, copying and pasting like 15 lines of code every time.
So, instead, let's encapsulate that by extending Parse.Object and overriding its fetch function, like so:
/*
models/post.js
*/
window.myApp = window.myApp || {};
window.myApp.Post = Parse.Object.extend("Post", {
fetch: function(options) {
var _arguments = arguments;
this.commentsQuery = this.relation("comments").query();
return this.commentsQuery.find({
success: (function(_this) {
return function(comments) {
return _this.comments = comments;
};
})(this)
}).always((function(_this) {
return function() {
return _this.constructor.__super__.fetch.apply(_this, _arguments);
};
})(this));
}
});
Disclaimer: you have to really understand how closures and IIFEs work, in order to fully grok how the above works, but here's what will happen when fetch is called on an existing Post, at a descriptive level:
Attempt to retrieve the post's comments and set it to the post's comments attribute
Regardless of the outcome of the above (whether it fails or not) operation, always perform the post's default fetch operation, and invoke all of that operation's callbacks

Categories

Resources