node.js bind callback issue - node.js

I have the following code :
let bindings =
{
on : (messageName,callback) =>
{
bindings[messageName] = callback
}
}
bindings.on('test',(params) =>
{
setTimeout( () =>
{
console.log("call id " , params.callId)
},~~(Math.random()*100))
})
let data = {callId : 1 }
for (let i=0;i<5;i++)
{
bindings['test'](data)
data.callId++
}
it produces the output
call id 6
call id 6
call id 6
call id 6
call id 6
call id 6
I know this issue can be solved with a bind https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/bind , but I cannot find the correct way to implement this and keep the actual design
adding a const fix the issue but I would like to find a more elegant/generic way to fix the issue
bindings.on('test',(params) =>
{
const callId = params.callId
setTimeout( () =>
{
console.log("call id " , callId)
},~~(Math.random()*100))
})

It is less about the implementation of your event logic but on the invocation of it using a reference that is being updated by a loop.
Since the implementation is an async execution due to the internal setTimeout, the invocation loop executes completely before the first event logic is executed. So because the input, params is a reference to the object data, when the loop is completed the callId value is now 6 which when referenced by any of the async calls will be that value.
Essentially the for loop is queuing up 5 async operations where each one is using the same referenced object, data. The loop is also updating a property value of the data object for each iteration. Because of how JavaScript handles asynchronous operations, the loop will complete before any of the asynchronous operations begin because it is still following the initial synchronous execution of the script.
One way to get around this is to create a new input object for each loop iteration:
let bindings = {
on: (messageName, callback) => {
bindings[messageName] = callback
}
}
bindings.on('test', (params) => {
setTimeout(() => {
console.log("call id ", params.callId)
}, ~~(Math.random() * 100));
});
for (let i = 0; i < 5; i++) {
bindings['test']({callId: i + 1});
}

Related

Why is my callback function receiving incorrect parameter values?

I have a function (called rankCheck), which takes three parameters:
Guild object (aka a Discord server)
UserId
Callback Function
The function will fetch the last 500 messages from every text channel in the provided guild. It will then will then only keep any messages that start with "!rank" and were sent by the provided UserId. Finally, it will count the remaining messages and pass the integer to the callback function.
async function rankChecks(guild, userId = *REMOVED FOR PRIVACY*, callback){
sumOfRankChecks = 0;
guild.channels.cache.each(channel => { //for each text channel, get # of rank checks for userId in last 500 msgs.
if (channel.type === "text"){
fetchMessages(channel, 500).then(msgs => {
let filteredMsgs = msgs.filter(msg => msg.content.startsWith("!rank") && msg.member.user.id == userId);
sumOfRankChecks = sumOfRankChecks + filteredMsgs.length;
});
}
});
callback(sumOfRankChecks);
}
Since discord only allows fetching 100 messages at once, I use this function (fetchMessages) to bypass this limit, by sending multiple requests, and then combining the results into one.
async function fetchMessages(channel, limit) {
const sum_messages = [];
let last_id;
while (true) {
const options = { limit: 100 };
if (last_id) {
options.before = last_id;
}
const messages = await channel.messages.fetch(options);
sum_messages.push(...messages.array());
last_id = messages.last().id;
if (messages.size != 100 || sum_messages >= limit) {
break;
}
}
return sum_messages;
}
When I call the rankCheck function, the return value is always 0
rankChecks(msg.guild, *REMOVED FOR PRIVACY*, function(int){
console.log(int);
});
Output:
0
However when I add a console.log into my rankCheck function:
async function rankChecks(guild, userId = *REMOVED FOR PRIVACY*, callback){
sumOfRankChecks = 0;
guild.channels.cache.each(channel => { //for each text channel, get # of rank checks for userId in last 500 msgs.
if (channel.type === "text"){
fetchMessages(channel, 500).then(msgs => {
let filteredMsgs = msgs.filter(msg => msg.content.startsWith("!rank") && msg.member.user.id == userId);
sumOfRankChecks = sumOfRankChecks + filteredMsgs.length;
console.log(sumOfRankChecks) //NEW CONSOLE.LOG!!!!!!!!!!!!!!!
});
}
});
callback(sumOfRankChecks);
}
Output:
3
5
This is the output I was expecting. Since I have 2 text channels in my server, I got 2 logs. If you had 3 channels, you would get 3 logs, etc. 3 messages from channel #1, and 2 messages from channel #2, therefore in total, there are 5 messages.
5 should be the integer that is passed into the callback function, but 0 is passed instead. Why is this?
Your callback function is being called before you even change sumOfRankChecks. Collection#each (and Map#forEach() and the gang) cannot wait for Promises to resolve because of the way they're built. Your code also wouldn't wait anyway, because you're not using await.
Despite what one might think is happening, guild.channels.each() is called, and callback() is called immediately after. This is the source of your confusion.
For more about async vs sync, you can check out the explanation in my answer here. You must use a for loop and await, or refactor your code so that async/await syntax is not necessary.
NOTE: The Discord.js documentation hyperlinked is for recently released v12. If your Discord.js isn't up to date, switch to the correct version at the top of the page for accurate info.

Timeout change args without re setTimeout

const timer = setTimeout(({a, b}) => {
console.log(a + b)
}, 3000, {a:1, b:2});
setTimeout(() => {
Object.assign(timer._timerArgs,[{a:2, b:2}])
}, 1000)
// Output: 4
Please have a look at this. What I'm going to do is, going to change the timer args if needed before it's called.
I don't want to use clearTimeout and setTimeout again for this process.
But not sure this is the right way. And plus how can I set the priority per each timer in case the timeout will be the same.
I don't know where you got ._timerArgs from. I've never seen that. Without dipping into undocumented properties (that are only present in node.js), you can do it like this:
const objA = {a:1, b:2};
const timer = setTimeout(({a, b}) => {
console.log(a + b);
}, 500, objA);
objA.a = 2;
objA.b = 3;
This will output 5 which reflects that you changed the property values before the timer callback fired.
Since objects in Javascript are passed by pointer (not copied), you can still modify the object that objA points at any time before the timer fires and see the effect inside the timer callback.
But, then you don't even have to pass the arguments into the setTimeout(). You can just reference a parent scoped variable in the callback:
const objA = {a:1, b:2};
const timer = setTimeout(() => {
console.log(objA.a + objA.b);
}, 500);
objA.a = 2;
objA.b = 3;
This will also output 5.
If you want arbitrary argument modification (not properties embedded in an object), and you want it to be only using supported, standard tools that work in all implementations of Javascript, then you can't do that with just setTimeout(). You could make your own timer wrapper though:
class MyTimer {
constructor(fn, t, ...args) {
this.args = args;
this.fired = false;
this.timer = setTimeout(() => {
this.fired = true;
fn.apply(null, this.args);
}, t);
}
cancel() {
clearTimeout(this.timer);
}
hasFired() {
return this.fired;
}
}
const timer = new MyTimer((...args) => {
console.log("timer callback arguments:", args);
}, 500, "hello", "goodbye");
timer.args = ["ola", "adios", "amor", "amigo"];
You can then modify the array in the .args property at any time before the timer fires and they will be passed to the timer callback.

How to load data from a Firestore query into a variable at the global scope for later use

Like my question title says, I'd like to load a firestore query result into a variable at my global scope for later use.
I want to use the value later in a dynamic listener and I don't want to have to re-query it again and again.
I've tried looking on google for a simple solution. I've already tried implementing my own solution with promise, callbacks, and async await but to no avail.
This is from the github documentation that shows how to do what I want, but without a query.
https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/functions/tips/index.js
const heavyComputation = () => {
// Multiplication is more computationally expensive than addition
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return numbers.reduce((t, x) => t * x);
};
const functionSpecificComputation = heavyComputation;
const fileWideComputation = lightComputation;
// [START functions_tips_scopes]
// [START run_tips_global_scope]
// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();
Here is my own attempt
const getNBAScoreKey = () => {
return new Promise(resolve => {
scoresRef.onSnapshot(nbaScoreKeySnapshot => {
console.log("the value inside the score key: " + nbaScoreKeySnapshot.data()["FantScores"]);
resolve(nbaScoreKeySnapshot.data());
});
});
}
I expect the variable to be an object with the data, but instead whatever implementation I try I get 'undefined'.
You can set variable in global scope and reassign it's value in scoresRef.onSnapshot but If you are trying to access this value immediately without waiting for data to be fetched from database, of course you will get undefined because data not fetched yet.
So in your case you have to use observable design pattern, by using lib like rxjs or implementing it yourself,
Observable lets you define an variable and subscribe on value change events.
Simple observer implementation
let observer = {
value: {} ,
subscribers: [] ,
subscribe: function (cb ) { this.subscribers.push(cb)} ,
notify: function (data) { this.value = data ; this.subscribers.forEach( s => s(data);}
}
to subscribe on value changes, you have to call observer.subscribe and pass callback function to be fired on data changes
observer.subscribe((data)=> { console.log('first', data ) } ) // first subscriber
observer.subscribe((data)=> { console.log('sec', data ) } ) // sec subscriber
// to notify subscriber that value has been changed
observer.notify(123)
the output will be
first 123123
sec 123123
Your case you have to subscribe first any where on that observer
observer.subscribe((data)=> {
console.log('I got some data from firestore', data );
// Do some stuff
} );
and just add notify in your fetch function
const getNBAScoreKey = () => {
return new Promise(resolve => {
scoresRef.onSnapshot(nbaScoreKeySnapshot => {
console.log("the value inside the score key: " + nbaScoreKeySnapshot.data()["FantScores"]);
let data = nbaScoreKeySnapshot.data();
observer.notify(data);
resolve(data);
});
});
}
also you can get data in any time by calling observer.data will return the latest data
make sure you have defined observer in global scope, or on separate file and export observer variable then import that file anywhere the data will be shared

Call a generator function inside setInterval()

I am trying to call a generator function inside setInterval() method. The objective of this code is it will query a particular server for some data periodically, until it gets a non zero response. Upon getting the response it will call storeAddress() which is a generator function defined in the same file.
The below code is giving me an error like this:
SyntaxError: yield is a reserved word (248:6)
NOTE: I am using react-boilerplate to build my app. The above error is thrown by babel, as far as I can tell through searching internet.
I have tried const query = yeild call (setInterval, function(){magic code}, 10000). This does not give the error, but magic code never gets executed.
I have tried const query = setInterval(function* () {magic code}, 10000) with the same effect as above.
I have tried const query = setInterval(yield call(function(){magic code}, 10000) with same effect as above.
I have tried const query = yield call (setInterval, function(){magic code}, 10000) with same effect as above.
I have tried storeAddress(action.payload, balance).next() inside setInterval(). The control does flow inside storeAddress(), but that function also have generator calls inside, which never gets invoked. In fact nothing after the first generator call inside storeAddress() gets executed in this case.
function* callSaveNewAddress(action){
const selectedNetwork = yield select(selectNetworkId());
let count = 1;
let balance = 0;
const query = setInterval(function () {
getAddressBalance(action.payload, selectedNetwork).then(res =>
{return balance = res.data.mempool_balance});
if(balance > 0) {
yield call (storeAddress, action.payload, balance);
clearInterval(query);
} else if(count == 90) {
clearInterval(query);
console.log("Nothing received in 15 minutes");
}
}, 10000);
}
So how am I suppose to call storeAddress(), which is a generator function, inside a normal function like setInterval()?
const query= function * () {
const runner = yield call(setInterval, () => {
getAddressBalance(action.payload, selectedNetwork).then(res =>
{return balance = res.data.mempool_balance});
if(balance > 0) {
yield call (storeAddress, action.payload, balance);
clearInterval(query);
} else if(count == 90) {
clearInterval(query);
console.log("Nothing received in 15 minutes");
}
}, 1000);
}
try to use the setInterval within a call, passing through parameters the function you want to execute within it.

How to run asynchronous tasks synchronous?

I'm developing an app with the following node.js stack: Express/Socket.IO + React. In React I have DataTables, wherein you can search and with every keystroke the data gets dynamically updated! :)
I use Socket.IO for data-fetching, so on every keystroke the client socket emits some parameters and the server calls then the callback to return data. This works like a charm, but it is not garanteed that the returned data comes back in the same order as the client sent it.
To simulate: So when I type in 'a', the server responds with this same 'a' and so for every character.
I found the async module for node.js and tried to use the queue to return tasks in the same order it received it. For simplicity I delayed the second incoming task with setTimeout to simulate a slow performing database-query:
Declaration:
const async = require('async');
var queue = async.queue(function(task, callback) {
if(task.count == 1) {
setTimeout(function() {
callback();
}, 3000);
} else {
callback();
}
}, 10);
Usage:
socket.on('result', function(data, fn) {
var filter = data.filter;
if(filter.length === 1) { // TEST SYNCHRONOUSLY
queue.push({name: filter, count: 1}, function(err) {
fn(filter);
// console.log('finished processing slow');
});
} else {
// add some items to the queue
queue.push({name: filter, count: filter.length}, function(err) {
fn(data.filter);
// console.log('finished processing fast');
});
}
});
But the way I receive it in the client console, when I search for abc is as follows:
ab -> abc -> a(after 3 sec)
I want it to return it like this: a(after 3sec) -> ab -> abc
My thought is that the queue runs the setTimeout and then goes further and eventually the setTimeout gets fired somewhere on the event loop later on. This resulting in returning later search filters earlier then the slow performing one.
How can i solve this problem?
First a few comments, which might help clear up your understanding of async calls:
Using "timeout" to try and align async calls is a bad idea, that is not the idea about async calls. You will never know how long an async call will take, so you can never set the appropriate timeout.
I believe you are misunderstanding the usage of queue from async library you described. The documentation for the queue can be found here.
Copy pasting the documentation in here, in-case things are changed or down:
Creates a queue object with the specified concurrency. Tasks added to the queue are processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one becomes available. Once a worker completes a task, that task's callback is called.
The above means that the queue can simply be used to priorities the async task a given worker can perform. The different async tasks can still be finished at different times.
Potential solutions
There are a few solutions to your problem, depending on your requirements.
You can only send one async call at a time and wait for the first one to finish before sending the next one
You store the results and only display the results to the user when all calls have finished
You disregard all calls except for the latest async call
In your case I would pick solution 3 as your are searching for something. Why would you use care about the results for "a" if they are already searching for "abc" before they get the response for "a"?
This can be done by giving each request a timestamp and then sort based on the timestamp taking the latest.
SOLUTION:
Server:
exports = module.exports = function(io){
io.sockets.on('connection', function (socket) {
socket.on('result', function(data, fn) {
var filter = data.filter;
var counter = data.counter;
if(filter.length === 1 || filter.length === 5) { // TEST SYNCHRONOUSLY
setTimeout(function() {
fn({ filter: filter, counter: counter}); // return to client
}, 3000);
} else {
fn({ filter: filter, counter: counter}); // return to client
}
});
});
}
Client:
export class FilterableDataTable extends Component {
constructor(props) {
super();
this.state = {
endpoint: "http://localhost:3001",
filters: {},
counter: 0
};
this.onLazyLoad = this.onLazyLoad.bind(this);
}
onLazyLoad(event) {
var offset = event.first;
if(offset === null) {
offset = 0;
}
var filter = ''; // filter is the search character
if(event.filters.result2 != undefined) {
filter = event.filters.result2.value;
}
var returnedData = null;
this.state.counter++;
this.socket.emit('result', {
offset: offset,
limit: 20,
filter: filter,
counter: this.state.counter
}, function(data) {
returnedData = data;
console.log(returnedData);
if(returnedData.counter === this.state.counter) {
console.log('DATA: ' + JSON.stringify(returnedData));
}
}
This however does send unneeded data to the client, which in return ignores it. Somebody any idea's for further optimizing this kind of communication? For example a method to keep old data at the server and only send the latest?

Resources