nodejs do something after multiple functions complete - node.js

I'm having a problem that I can't seem to get around.
Imaging the following
if (users) {
for (var i = 0; i < users.length; i++) {
apiData.users = getUserDetails(users[i]);
}
}
and
if(profiles) {
for (var i = 0; i < profiles.length; i++) {
apiData.profiles = getProfileDetails(users[i]);
}
}
now, only once both of those blocks of code have been completed (i.e. the API calls have returned the results that are required), would I like to do something like this
for (var i = 0; i < users.length; i++) {
saveUserProfile(apiData)
}
Normally I handle a situation like this by wrapping the first block of code in a function that returns a callback and then running the third function afterwards, but how can I do this when there are effectively 2 separate operations happening?
To be clear, I would like to avoid saving users and profiles separately, there are various reasons for this.
I would like to know if there is any standard way of handling a situation like this as there may or may not be users or profiles, so using callbacks seems like it will not work?

You can use async module to achieve this.
Use async.parallel() function since your first and second snippets do not depend on each other, and can run asynchronously, but you wish to make third snippet run when first and second are done executing.
Assuming users, profiles, and apiData are already declared, your code would look like
function func1(callback){
if (users) {
for (var i = 0; i < users.length; i++) {
apiData.users = getUserDetails(users[i]);
}
}
//invoke callback
}
function func2(callback){
if(profiles) {
for (var i = 0; i < profiles.length; i++) {
apiData.profiles = getProfileDetails(users[i]);
}
}
//invoke callback
}
var asyncfunc = [];
asyncfunc.push(func1);
asyncfunc.push(func2);
async.parallel(asyncfunc,function(err,result){
//result is an array which receives the second argument of each function's callback.
for (var i = 0; i < users.length; i++) {
saveUserProfile(apiData)
}
})
EDIT
PS: you can also use async.series() here. Both are equivalent in this case, since the functions handled by async.parallel() here do not use timers neither perform I/O tasks.
The callback in each function inside the array takes two arguments. First argument represents error, which is null if there are no errors, second represents the result of the function, which is passed to the second argument of the async.parallel() function's callback.

Related

Asynch, await, callback - What exactly is the execution context of a callback function?

So, I have previous programming experience in numerous languages: assembly(s), c, c++, basic(s), page description language(s), etc.
I am currently learning node, js, puppeteer and have run into something I can not quite make sense of.
I have read various things that seem explain various limitations of the callback execution context, but I have not found anything specifically that explains this.
I am attempting to call functions or reference variables (defined in the current module) from within a callback function. I have tried a number of variations, I have tried with variables of assorted types defined in assorted locations - but this one demonstrates the problem and I expect the solution to this will be the solution for all the variants. I am getting errors that "aFunction is not defined".
Why can't the callback function see the globally defined function "aFunction()"
function aFunction(parm)
{
return something;
}
(async () => {
let pages = await browser.pages();
// array of browser titles
var titles = [];
// iterate pages extracting each title using forloop because foreach can not contain await.
for (let index = 0; index < pages.length; index++) {
const pagex = pages[index]
const title = await pagex.title();
titles.push(title);
}
//chopped and edited a bunch to keep it simple
// here is the home of my problem.
foundAt = 0;
const container = await pages[foundAt].evaluate(() => {
let elements = $('.classiwant').toArray();
// this is the failing call
var x = aFunction(something);
for (i = 0; i < elements.length; i++) {
$(elements[i]).click();
}
})

It's sends 4 times the amount of members

So I want my bot in a certain server to send a dm to a random member every 10 minutes. And when my bot has sent everyone from the server a dm it sends a complete message.
But when i start the bot it sends 4 times the amount of members
if (message.content.startsWith(botconfig.prefix + 'dmall')) {
console.log("demo");
var list = message.guild.members.array();
sendMessage(list);
}
});
function sendMessage(list) {
setTimeout(function () {
for (i = 0; i < list.length; i++) {
console.log(list.length);
}
console.log("I'm done, mate!");
sendMessage(list);
}, 10 * 1000);
}
CONSOLE:
demo
4 (is the amount of the members)
4
4
4
I'm done mate!
This part of your code:
for (i = 0; i < list.length; i++) {
console.log(list.length);
}
tells Javascript to run the statement:
console.log(list.length);
list.length times. If list.length is 4 which it appears to be here, then you will see in the console
4
4
4
4
That's what the code was instructed to do.
I don't see any reason why you'd put that in a loop unless you want to output each array element separately. So, if you want to just output the length once, then replace this:
for (i = 0; i < list.length; i++) {
console.log(list.length);
}
with this:
console.log(list.length);
In addition, if you were to use a for loop, you MUST declare all variables that you use. So, this:
for (i = 0; i < list.length; i++) {
is very dangerous. It relies on a higher scoped i which can easily conflict with other higher scoped i variables and create hard-to-figure out bugs. Every for loop should declare it's own loop variable as in:
for (let i = 0; i < list.length; i++) {
If you run your code in strict mode (which you should), the above for loop declaration would likely cause an error (which is a good thing because you'd immediately see the coding error and fix it).
There are a couple of things wrong here.
For of: JavaScript variable scoping messing you up (Different example here
Second of all: setTimeout is not the same as setInterval.
Completely fixing your issue would be:
// Use the discord API
let users = ['justme']
function getUsers() {
// Mutate just to display a new one each time
users = [...users, users.length.toString()];
return users
}
function notifyUsers() {
// Retrieve all users now
const currentUsers = getUsers()
// Send a message to each user
currentUsers.forEach(user => sendMessage(user))
console.log('I have sent everyone a message')
}
function sendMessage(user) {
console.log(`Sending message to ${user}`)
}
// Start the application by looping every <whatever>
setInterval(() => notifyUsers(), 1000)

how can call another function after excuted multiple function in loop node js

Please find below code
function get_btc(address) {
address_transaction(address, user_id, coin_key, deposite_txn_fee, function(callback) {
for (var j = 0; j < callback.response.data.txs.length; j++) {
let user_id = callback.user_id;
//some code//
}
});
}
get_label_info(function(err, data) {
for (var i = 0; i < data.length; i++) {
let address = data[i].address;
deposite_model.get_coin_info(function(err, data1) {
var coin_name = data1[0].coin_code;
const return_functions = get_switch(coin_name);
if (return_functions) {
obj[return_functions](address);
}
})
}
});
function all_completed() {
console.log('all functions has been completed');
}
By the help of above mentioned code i want to excecute all_completed loop when all functions has been completly done.
At the initial start get_label_info function is excuted then controller go on to get_btc function.
Please help me how could i run all_completed functions after all functions completed run.
I'll assume you are using es6, and that you know what a Promise is in that context. In that case wrap all your callback based things in a Promise that resolves when the callback completes. Then, in your loop, push all your Promises into an array variable. Finally call Promise.all with that array as an argument and call then on the result to encapsulate the code you want ti run after they all complete (resolve).

Is there a simple way to do an async forEach over an array without a library in node?

I don't want to have to npm anything. I'm just trying to create a script to process a few files but as I have to use streams to process the files I need to use some form of async forEach.
The issue is I just want a simple .js file I can run, I don't want to have to npm install a bunch of stuff.
If you want, for example, the forEach functionality of async (https://github.com/caolan/async), you can always look into its' source and reimplement it yourself.
A simplistic implementation could look like this:
function forEach(arr, fun, callback) {
var toDo = arr.length;
var doneCallback = function() {
--toDo;
if(toDo === 0) {
callback();
}
}
for(var i = 0, len = arr.length; i < len; ++i) {
fun(arr[i], doneCallback);
}
}
It assumes the function you want to run takes an array element and a callback. You could modify it to collect results, handle errors, etc. I encourage you to look into async's source.

Migrating from localStorage to chrome.storage

I'm in the process of migrating my Chrome extension's persistency repository, from localStorage to chrome.storage. An important difference between them is that chrome.storage is asynchronous, therefore a callback function needs to be passed.
How would you modify a loop that writes to localStorage, synchronously, to the async chrome.storage?
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
Thanks.
For this example, I'll use chrome.storage.local, but you can replace it with chrome.storage.sync if you prefer.
The goal is to use chrome.storage.local.set. The first step is to convert your obj into an object that contains the list of pair of key / value:
var keyValue = {};
for (var i = 0; i < obj.length; i++)
{
keyValue[obj[i].key] = obj[i].val;
}
Then we call the actual method:
chrome.storage.local.set(keyValue, function()
{
// Notify that we saved.
doThisWhenAllElementsAreSaved();
});
or, simply:
chrome.storage.local(keyValue, doThisWhenAllElementsAreSaved);
Note that the callback will be called on success, as well as on failure. If the storage failed, then chrome.runtime.lastError will be set.
You can save multiple items at once with the chrome.storage API, so I would use the following approach:
Before, using localStorage.setItem
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
After, using chrome.storage.local.set
var items = {};
for (var i = 0; i < obj.length; i++) {
items[obj[i].key] = obj[i].val;
}
chrome.storage.local.set(items, function() {
doThisWhenAllElementsAreSaved();
});
If you need to know whether the save operation succeeded, check the value of chrome.runtime.lastError in the callback.
The best practice would be just to change the code to use chrome.storage.local like others said.
But sometimes, it gets really messy and you may want some code to remain untouched.
I tried to use a library that highly incorporates window.localStorage in a packaged app environment(the library was angular-cached-resource) and didn't want to change the original source code. So I managed to make the following shim: https://github.com/stewartpark/chrome-localStorage-shim
Hope it helps! Happy hacking.

Resources