understanding javascript code with callbacks - javascript

Below you see a part of the server-side code of the twich.me node.js chat:
exports.channel = function(MESSAGE_BACKLOG, MESSAGE_TRUNCATE) {
return (function() {
var messages = [],
callbacks = [];
return {
appendMessage : function (nick, room, type, text) {
//truncate message if necessary
if (type == 'msg' && text.length > MESSAGE_TRUNCATE) {
text = text.substr(0, MESSAGE_TRUNCATE) + "... (trunc.)";
}
//message
var m = {
nick: nick,
type: type, // "msg", "join", "part"
text: text,
room: room,
timestamp: (new Date()).getTime()
};
//output to console
// mlog(m);
//push msg on message stack
messages.push( m );
//???
while (callbacks.length > 0) {
callbacks.shift().callback([m]);
}
//old messages get pushed out of message stack
while (messages.length > MESSAGE_BACKLOG) {
messages.shift();
}
},
query : function (room, since, callback) {
var matching = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
if (message.timestamp > since && room == message.room) {
matching.push(message)
}
}
//???
if (matching.length != 0) {
callback(matching);
}
else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
},
//run initially when script starts
init : function() {
// clear old callbacks older than 25 seconds (lowered from 30 seconds to get round rmit proxy server's 30sec timeout
setInterval(function () {
var now = new Date();
while (callbacks.length > 0 && now - callbacks[0].timestamp > 25*1000) {
callbacks.shift().callback([]);
}
}, 3000);
return "hi";
}
}
}());
}
The code is responsible for storing and retrieving chat messages from one of the chat rooms.
I am not a javascript programmer. My background is with PHP where everything is procedural. I want to solve this with memcached instead. But first I need to understand what exactly is going on. I have added extra comments. What I don't understand is all the stuff with the callbacks. Could you help me understand what the callbacks are doing?

I don't really understand what you want but here's what going on:
while (callbacks.length > 0) {
callbacks.shift().callback([m]);
}
while the amount of objects in the array callbacks is greater than 0,
callbacks.shift() function will apparently return an object with a property called callback which is a function. and it's calling that function with an array that has the variable m in it.
if (matching.length != 0) {
callback(matching);
}
else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
}
if the amount of objects in the array matching is not 0, call the function callback or if it does, cal the function callback.push with an object.

Related

Why does my forEach() loop only execute once?

I seem to have encountered a problem while looping through an array. The loop seems to only execute once, no matter the size of the array. I tried using different methods of looping and the error still persists.
As background information, I'm trying to make a bot with which users can award each other points. Everything else seemed alright. The only issue is that I wish to set up a maximum amount of points one user can give to another in a day, and I'm having problems looping through the array which stores this information.
These are the relevant parts of my code:
var timer = []; //Timer stores the values.
const getTimerSenderIdTable = (id) => {
let found = false;
timer.forEach(function(dat) { // This is the problematic loop.
if (dat.id === id) {
found = dat;
}
})
console.log("loop end, results: " + found);
return found;
};
const timerManager = (senderId, targetId, pointSurp) => { //All arguments are integers.
let d = new Date()
if (getTimerSenderIdTable("date") !== d.getDate()) {
timer = [];
timer.push({"id":"date", "time":d.getDate()});
if (getTimerSenderIdTable("date")) {
if (getTimerSenderIdTable(senderId)) {
console.log("path 1");
} else {
console.log("path 2");
timer.push({"id":senderId, [targetId]:pointSurp});
}
}
} else {
if (getTimerSenderIdTable("date")) {
if (getTimerSenderIdTable(senderId)) {
console.log("path 3");
} else {
console.log("path 4");
timer.push({"id":senderId, [targetId]:pointSurp});
}
}
}
console.log(timer)
};
*Edit:
Thank you for your comments. Here is an example:
Calling timerManager(123456, 654321, 3) will produce the following output:
loop end, results: false
loop end, results: [object Object]
loop end, results: false
path 2
[ { id: 'date', time: 28 }, { '654321': 3, id: 123456 } ]
(This is a repost from comments. My appologies.)
It seems because of this line
if (getTimerSenderIdTable("date") !== d.getDate()) {
timer = [];
This will empty the array and next lines of code will only push single element
as #mbojko has pointed out, you'll want to use the find method for returning the found obj inside getTimerSenderIdTable function, like this
const getTimerSenderIdTable = (id) => {
return timer.find(item => item.id === id});
};

nodejs callback in a function - newbie query

I have the following function to extract some variables from a html page and publish them via i/o which is working properly - but I want to add a callback to ensure that I can find out if this function has completed fully.
Please advise how i can add a callback purely for this purpose - since I don't have any other need of the callback.
function RunScrapingPositions() {
status = false;
myhttp.get('https://example.com/PB.jsp',
function (_html) {
if (_html && _html.length > 10) {
news.positions = {};
$ = cheerio.load(_html);
$('tr[id^="TR"]').each(function () {
status = true;
var symbol = $('td:nth-child(3)', this).text().trim();
var objob = {
'NQ': parseInt($('td:nth-child(11)', this).text().trim()),
};
var post = {
'symbol': symbol,
'nq': objob.NQ
};
connection.query('INSERT INTO NP SET ?', post, function (err,result){
if (err)
{console.log("NP sql insert error : " +symbol);}
else {
console.log("Posn - Step 3B - Position data inserted into NP Table : " +symbol);
}
});
var objstock = news.analysis[symbol];
if (typeof objstock!='undefined') {
objstock.NQ = objob.NQ;
news.positions[symbol] = objob;
news.analysis[symbol] = objstock;
if (status) {
console.log('Posn - Step 4 - Positions data pushed to page')
io.emit('news', news);
}
}
else
{
console.log('Posn - Step 4A - Position symbol not found');
}
});
if (timerPositions) {
clearTimeout(timerPositions);
}
console.log('Posn - Step 5 - setTimer RunScrapingPositions:' + config.DelayExtractPositions);
timerPositions = setTimeout(RunScrapingPositions, config.DelayExtractPositions);
}
});
}
You could have a callback implemented with the following steps:
Accept a callback function as argument:
function RunScrapingPositions(callbackDone) {
Then, where you call it, pass that function on. You can use bindfor that. And where you have the condition that all is done, call it:
function RunScrapingPositions(callbackDone) {
status = false;
myhttp.get('https://example.com/PB.jsp', function (_html) {
if (_html && _html.length > 10) {
// ...
// pass the callback function on to the deferred call:
timerPositions = setTimeout(
RunScrapingPositions.bind(null, callbackDone),
config.DelayExtractPositions
);
} else {
// all is done, call the callback function:
callbackDone();
}
});
}
You can also use jQuery's custom event facility, $(document).trigger({eventName}, obj or list of arguments), to trigger an "event". You can write code to listen for that event via $(document).on({eventName}, function(someArgsThatYouPass){do something here....}).

Cannot break recursion with $.Deffered() object and $.then()

I have to search through word index tables with potentially hundreds of thousands rows. I can restrict my search by passing a list of documents to the search. Request to search for words in many documents return very slowly. So...to improve the UX we're chunking the request into several groups of documents. So, if a user asks to search 90 documents, and the chunk size is 10 documents per query, then we send out 90 / 10 = 9 independent $.ajax() calls. We want the results to come in the order they were sent.
We implement this recursion:
var SearchFunction = function () {
$.ajax(/* ... */);
}
var RecursiveSearch = function () {
var deferred = $.Deferred();
if (arrTransSearch.length > 0) {
deferred = SearchDocuments(arrTransSearch.shift());
}
else {
deferred.reject();
}
return deferred.promise().then(RecursiveSearch);
}
if (arrTransSearch.length > 1) {
RecursiveSearch().fail(SomeFunction);
}
var SomeFunction = function () {
alert("Failed. Yes!");
}
When I debug the code, it appears that deferred.reject() does not change the state of deferred.promise(). That is, when the next line
return deferred.promise().then(RecursiveSearch)
is executed, it just loops back into the recursive function, instead of exiting the recursion and falling into
RecursiveSearch().fail(SomeFunction);
Important Note:
I'm using jQuery-1.7.1. I ran analogous recursion in JSFiddle (Thank you Beeetroot-Beetroot) and it failed on jQuery-1.7.2 while on jQuery-2.1.0 it ran without a problem.
Any idea on how to get the recursion to work in jQuery-1.7.1?
A pattern partly covering what you are looking for is provided here under the heading "The Collection Kerfuffle". You actually need slightly more than that because you want to address your list of document referneces in chunks (groups).
The code will be something like this :
$(function() {
//General ajax options for searching a document group
var ajaxOptions = {
url: '...',
type: 'POST',
//data: ... //added dynamically
dataType: 'JSON',
// etc.
};
//
function searchDocumentsInGroups(arr, n) {
//Pre-process arr to create an array of arrays, where each inner array is a group of document references
var groups = [];
$.each(arr, function (i) {
if (!(i % n)) groups.push(arr.slice(i, i + n));
});
//Ajax serializer (from the Collection Kerfuffle reference)
return groups.reduce(function (promise, group) {
return promise.then(function () {
return $.ajax($.extend({}, ajaxOptions, {
data: JSON.stringify(group);//or whatever, compatible with the server-side script
})).then(function (groupResults) {
//display groupResults here
});
});
}, $.when(0));
}
// data
var myDocumentArray = [ 'doc1', 'doc2', 'doc3', 'doc4', 'etc.' ], //Your array of 90 document references.
groupSize = 10; //Number of documents per "chunk".
// Event handler to kick off the process.
$("#searchDocuments").on('click', function () {
// display "in progress" message or spinner here
searchDocumentsInGroups(myDocumentArray, groupSize).then(function () {
// display "complete" message or hide spinner here
});
});
});
You also need the Polyfill for Array.prototype.reduce, as .reduce is relied on above and older browsers (pre ECMAScript5) don't have it.
if ( 'function' !== typeof Array.prototype.reduce ) {
Array.prototype.reduce = function( callback /*, initialValue*/ ) {
'use strict';
if ( null === this || 'undefined' === typeof this ) {
throw new TypeError(
'Array.prototype.reduce called on null or undefined' );
}
if ( 'function' !== typeof callback ) {
throw new TypeError( callback + ' is not a function' );
}
var t = Object( this ), len = t.length >>> 0, k = 0, value;
if ( arguments.length >= 2 ) {
value = arguments[1];
} else {
while ( k < len && ! k in t ) k++;
if ( k >= len )
throw new TypeError('Reduce of empty array with no initial value');
value = t[ k++ ];
}
for ( ; k < len ; k++ ) {
if ( k in t ) {
value = callback( value, t[k], k, t );
}
}
return value;
};
}
All untested but I recently answered a similar question here, with a link to a fiddle.
It turns out that until jQuery-1.8, calling $.then() with one argument was the equivalent of calling $.then(successFunction, successFunction). Since I was using jQuery-1.7.1, a rejected promise would still invoke the recursion.

Simpy cannot iterate over javascript object?

I have scoured the other question/answer for this and implemented everything and I still cannot access the values of the object. Here's the code I am using:
function apply_voucher(voucher) {
var dates = $.parseJSON($("[name='dates']").val());
var voucher_yes_no = new Array();
var voucher_reduction = new Array();
if(voucher.length > 0)
{
$.each(dates, function(room_id, these_dates) {
$.post('/multiroom/check_voucher/'+voucher+'/'+room_id, function(result) {
if(result.result == 'ok') {
voucher_yes_no.push('yes');
voucher_reduction.push(result.voucher_reduction);
} else {
voucher_yes_no.push('no');
}
}, 'json');
});
// check if there are any yes's in the array
if('yes' in voucher_yes_no) {
console.log("no yes's");
} else {
console.log(voucher_reduction);
console.log(typeof voucher_reduction);
for (var prop in voucher_reduction) {
console.log(prop);
console.log(voucher_reduction[prop]);
if (voucher_reduction.hasOwnProperty(prop)) {
console.log("prop: " + prop + " value: " + voucher_reduction[prop]);
}
}
}
}
}
Apologies for the constant console logging - I'm just trying to track everything to make sure it's all doing what it should. The console output I get from this is below:
...which shows the object containing one value, "1.01" and my console.log of the typeof it to make sure it is actually an object (as I thought I was going mad at one point). After this there is nothing from inside the for-in loop. I have tried jquery's $.each() also to no avail. I can't understand why nothing I'm trying is working!
It does not work because the Ajax call is asynchronous!
You are reading the values BEFORE it is populated!
Move the code in and watch it magically start working since it will run after you actually populate the Array!
function apply_voucher(voucher) {
var room_id = "169";
var dates = $.parseJSON($("[name='dates']").val());
var voucher_reduction = new Array();
$.post('/multiroom/check_voucher/'+voucher+'/'+room_id, function(result) {
if(result.result == 'ok') {
voucher_reduction.push(result.voucher_reduction);
}
console.log(voucher_reduction);
console.log(typeof voucher_reduction);
for (var prop in voucher_reduction) {
console.log(prop);
console.log(voucher_reduction[prop]);
if (voucher_reduction.hasOwnProperty(prop)) {
console.log("prop: " + prop + " value: " + voucher_reduction[prop]);
}
}
}, 'json');
}
From what it looks like, you plan on making that Ajax call in a loop. For this you need to wait for all of the requests to be done. You need to use when() and then(). It is answered in another question: https://stackoverflow.com/a/9865124/14104
Just to say for future viewers that changing the way I did this to use proper deferred objects and promises, which blew my head up for a while, but I got there! Thanks for all the help, particularly #epascarello for pointing me in the right direction :) As soon as I started doing it this way the arrays began behaving like arrays again as well, hooray!
Here's the final code:
function apply_voucher(voucher) {
var booking_id = $("[name='booking_id']").val();
var dates = $.parseJSON($("[name='dates']").val());
if(voucher.length > 0) {
var data = []; // the ids coming back from serviceA
var deferredA = blah(data, voucher, dates); // has to add the ids to data
deferredA.done(function() { // if blah successful...
var voucher_yes_no = data[0];
var voucher_reduction = data[1];
if(voucher_yes_no.indexOf("yes") !== -1)
{
console.log("at least one yes!");
// change value of voucher_reduction field
var reduction_total = 0;
for(var i = 0; i < voucher_reduction.length; i++) {
reduction_total += voucher_reduction[i];
}
console.log(reduction_total);
}
else
{
console.log("there are no yes's");
}
});
}
}
function blah(data, voucher, dates) {
var dfd = $.Deferred();
var voucher_yes_no = new Array();
var voucher_reduction = new Array();
var cycles = 0;
var dates_length = 0;
for(var prop in dates) {
++dates_length;
}
$.each(dates, function(room_id, these_dates) {
$.post('/multiroom/check_voucher/'+voucher+'/'+room_id, function(result) {
if(result.result == 'ok') {
voucher_reduction.push(result.voucher_reduction);
voucher_yes_no.push('yes');
} else {
voucher_yes_no.push('no');
}
++cycles;
if(cycles == dates_length) {
data.push(voucher_yes_no);
data.push(voucher_reduction);
dfd.resolve();
}
}, 'json');
});
return dfd.promise();
}
Can you show how voucher_reduction is defined?
I am wondering where the second line of the debug output comes from, the one starting with '0'.
in this line:
console.log(vouncher_reduction[prop]);
^
The name of the variable is wrong (then) and probably that is breaking your code.
I think there are no problem with your loop.
But perhaps with your object.
Are you sure what properties has enumerable ?
Try to execute this to check :
Object.getOwnPropertyDescriptor(voucher_reduction,'0');
If it return undefined, the property was not exist.

setTimeout for each instance of object

I have this object (this is only part of it, obviously...):
function room() {
this.data = {
sender_name: '',
room_id: 0
}
this.create = function (sender_name) {
// (code to set this.data)
this.removeTimer();
}
this.removeTimer = function() {
var t = this;
console.log(t.data.sender_name) // log #1
$.tGet('roomInfo', {'room_id': this.data.room_id}, function(edata) {
console.log(t.data.sender_name) // log #2
if(edata.exists == 'false') {
// remove room code...
}
else {
setTimeout(function() {
t.removeTimer();
}, 1000)
}
})
};
}
and I create its instances from DB like this:
$.tGet('getRooms', {'user_id': mUser}, function(edata) {
var i = 0, n;
for(i = 0; i<edata.length; i++) {
n = new room();
n.create(edata[i].sender_name);
}
})
Now, the problem is that what console prints is only the last room created. I don't know what's going on...
The first time this code runs, log #1 is completely correct. However, log #2 is already problematic because it gives me only the last room in the DB (or in the loop...) and, of course, after the timeout log #1 becomes wrong too.
What's going on? I'm really just a begginer at javascript, so...
tGet function:
$.tGet = function(action, data, callback, error) {
$.get('php/ajax.php?action='+action, data, callback, 'json').fail(error);
}
this.removeTimer = function() {
console.log(this.data.sender_name) // log #1
$.tGet('roomInfo', {
'room_id': this.data.room_id,
'instance' : this
}, function(edata) {
console.log(this.instance.data.sender_name) // log #2
if(edata.exists == 'false') {
// remove room code...
}
else {
setTimeout(function() {
instance.removeTimer();
}, 1000)
}
})
};
It looks like you're creating multiple room instances with the same room id . Should the create line read n.create(edata[i].room_id)

Categories