How can I chain Promise functions with dynamic length? - javascript

I have variable count of data in an array. I want something, to do this:
function tryThis(num: number): Promise<any> {
return new Promise ((resolve, reject) => {
if(num == 3){
resolve()
}else{
reject();
}
}
}
let arrayOfSomething : Array<number> = [1,2,3,4];
let chain;
// create the chain
arrayOfSomething.forEach( (element) => {
// add element to the chain
chain.catch(() => {
tryThis(element);
});
});
// run chain
chain
.then(() => {
// one of elemnts of the array was 3
})
.catch(() => {
// no "3" found in array
})
So, my goal is to create a Promise chain form an array with variable count of data and at the end catch if all of tryThis() function gives reject. If one of tryThis() function gives resolve in the chain, then jump to the end and exit with resolve.
I know, that my code is not correct, this is just to show what I want to do.
Can anyone help me, to do this?
Thanks!

Maybee you could use Promise.any from bluebird, or similar functionality from some other library: https://www.npmjs.com/package/promise.any or https://www.npmjs.com/package/promise-any.
Promise.any is rejected if all elements are rejected. So, from array of numbers you need to create array of promises, and then call Promise.any. So for example:
let arrayOfSomething = [1,2,3,4];
Promise.any(arrayOfSomething.map(x => tryThis(x))
.then(() => {
// one of elemnts of the array was 3
})
.catch(() => {
// no "3" found in array
})

Related

Access variable outside a function in Node js [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
var count=0;
function test2(callback) {
db.doc("Kerala/Pathanamthitta")
.listCollections()
.then((snap) => {
snap.forEach((collection) => {
var col = collection.id;
db.collection(`Kerala/Pathanamthitta/${col}`)
.where("completionStatus", "<", 3)
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
var data = doc.data();
console.log(data.place);
if (data.completionStatus == 0) count++;
});
});
});
})
.then(callback);
}
test2(function () {
console.log(count);
});
I want to print the final count after the execution of test2 function. It prints the statement but always 0, even if any updation happens inside function test2. I tried to do a callback() but still the same happens
Please help. Thanks in advance
You're misunderstanding how asynchronous functions and promise chains works. You're calling a promise chain and the callback right after each other.
db.doc(...)...
callback()
This ends up executing like so:
db.doc
callback
db.doc.then
at this point you have called callback BEFORE the resolution of the promise chain. You want to put the callback in the promise chain so that it is delayed until after all of that has finished. A good spot would be in another promise chain after the outer loop for a single log of the eventual count.
...
.then(snap => {
snap.forEach(collection => {...});
})
.then(callback);
...
This way after you've finished going over all of the snaps and finished counting the snapshots you'll print out the count in the correct order after both traversals.
BUT WAIT it's still printing 0. Why is that? Well we're not properly chaining our promises. We'll need to make sure that any promises we create in a promise properly chain so that when we do get to the logging step we've got a proper chain going.
Full code:
var count = 0;
function test2(callback) {
db.doc("Kerala/Pathanamthitta")
.listCollections()
.then((snap) => {
return Promise.all(
snap.map((collection) => {
var col = collection.id;
return db
.collection(`Kerala/Pathanamthitta/${col}`)
.where("completionStatus", "<", 3)
.get()
.then((snapshot) => {
return Promise.all(
snapshot.map((doc) => {
var data = doc.data();
console.log(data.place);
if (data.completionStatus == 0) count++;
})
);
});
})
);
})
.then(callback);
}
test2(function () {
console.log(count);
});
As per my comment, you could just make a function call after you're done counting.
//content above
snapshot.forEach(doc => {
var data = doc.data();
console.log(data.place);
if (data.completionStatus == 0) count++;
});
functionTwo(count);
// content below
functionTwo(count) {
console.log(count);
}

How to enforce promise in array loop

I'm trying to get the basics of how to implement promises and Promise.All within a given array loop, for example, from a firebase query, which can perform any number of actions within the loop, and then I have access to the results later. I can't seem to get the correct syntax, logic . below is a sample method in which I'd like to delay insert some items into an array and then have access to the array after the loop for processing.
I've researched different methods on Stackoverfllow. Some assign a promises = the snap.forEach loop and then somehow resolve this after completion. Others are creating a promise inside the loop. In my example below, I'm just using the settimeout to delay/create an async process.
testfirebaseData = () => {
let channels =[];
var promises=[];
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
snap.forEach(channelChild => {
promises.push(new Promise((resolve) => {
setTimeout(() => {
channels.push(channelChild.key);
resolve(channels)
},1000)
})
)
})
}).then (() => {
Promise.all(promises).then(() => {
console.log(channels[0])
})
}
}
I would expect the output of the above to show the first element in the "channels" array, but it's always coming back with "undefined"..as I clearly am not processing the promises correctly. What am i missing?
Here is sample example using your code style. Instead of objects within the array I've put numbers;
https://jsfiddle.net/th3vecmg/4/
var promises = [];
let channels =[];
[1, 2, 3, 4, 5].forEach((elm, i) => {
promises.push(new Promise((resolve) => {
setTimeout(() => {
channels.push(elm)
resolve(elm);
}, 1000)
}))
});
console.log(channels[0]) // gives undefined
Promise.all(promises).then(() => {
console.log(channels[0]) // gives 1
})
You should not be getting undefined inside the then because it's waiting for the promise to execute. You will only get undefined it you are trying to display the result outside the then scope
The code doesn't make really sense since you are displaying a global variable inside the then rather then relying on the output of the promise
If you want to delay execution by 1 then do some multiplication in the timeArgument of the setTimeout...
Your new Promise(...) should wrap the firebase call as that is the async operation. And not be in the callback. There is no async operations going on inside the callback just plain array push which is a sync op
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var channels=[];
snap.forEach(channelChild => {
channels.push(channelChild.key);
});
resolve(channels);
});
});
promise.then((channels)=>{
console.log(channels);
});
If you want a delay on the whole operation:
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var channels=[];
setTimeout(()=>{
snap.forEach(channelChild => {
channels.push(channelChild.key);
});
resolve(channels);
},1000);
});
});
To simulate nested async operations you would add the promises to an array like you were but resolve Promises.all() in the outer promise and resolve your data in the inner promises:
var promise = new Promise((resolve)=>{
firebase.database().ref('challengeResponses').child(day).once('value', snap => {
var snapPromises=[];
snap.forEach(channelChild => {
snapPromises.push(new Promise((snapresolve)=>{
setTimeout(()=>{
snapresolve(channelChild.key);
},1000)
});
});
resolve(Promise.all(snapPromises));
});
});

Function returning map before foreach ended

I have this little program that calculates totals by multiplying a rate and some hours.
The problem I am having is that the function getTasks() always return an empty map.
When I log the fields being entered in the map, they are not empty but they are entered after the function returns the map.
So I am a bit confused why this is happening.
function getTask(taskId) {
return rp({
uri: `${url}/tasks/${taskId}`,
auth: {
user: 'user',
password,
sendImmediately: false
},
json: true
}).then((task) => {
return task;
});
}
function getTasks(entries) {
const taskIds = [];
entries.forEach((entry) => {
const taskId = entry.day_entry.task_id;
if (!taskIds.includes(taskId)) {
taskIds.push(taskId);
}
});
const taskRateMap = new Map();
taskIds.forEach((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
console.log(taskRateMap);
}
});
});
return taskRateMap;
}
function calculateTasksTotals(id) {
return co(function* calculateTotalsGen() {
let total = 0;
const entries = yield getEntriesForProject(id);
const tasks = getTasks(entries);
entries.forEach((entry) => {
const rate = tasks.get(entry.day_entry.task_id);
total += rate * entry.day_entry.hours;
});
return total;
});
}
calculateTasksTotals(id)
There are multiple problems with your code:
First off, as soon as you have any asynchronous operation involved in a function, the result of that function is going to be asynchronous. You simply cannot return it synchronously. The async operation finishes sometime later. The function itself returns BEFORE the async operation finishes.
So, you return a promise from any function that uses an async operation and the caller uses that promise to know when things are done or to get the final result.
Your function getTask() is fine. It returns a promise. The .then() inside that function is redundant and not needed since task appears to already be the resolved value of the promise.
Your function getTasks() is trying to synchronously return taskRateMap, but as you've seen in testing, none of the promises have resolved yet so there are no values in taskRateMap yet. In my code version, I use Promise.all() internally to know when all the getTask() operations are done and I return a promise who's resolved value is the taskRateMap object.
The caller of getTasks() can then use that promise and a .then() handler to get access to the taskRateMap object.
Here's one way to implement getTasks():
function getTasks(entries) {
// get all unique task_id values from the entries array
const taskIds = Array.from(new Set(entries.map(entry => entry.day_entry.task_id)));
const taskRateMap = new Map();
// use Promise.all() to know when the whole array of promises is done
// use tasksIds.map() to build an array of promises
return Promise.all(taskIds.map(taskId => {
// make this promise be the return value inside the .map() callback
// so we will end up with an array of promises that will be passed to
// Promise.all()
return getTask(taskId).then(res => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
}
})
})).then(() => {
// make final resolved value be the taskRateMap
return taskRateMap;
});
}
getTasks(entries).then(taskRateMap => {
// use taskRateMap here
});
You have an issue with your Promises. Try using Promise.all() providing all the promises as input, and return your map only when all the promises have been resolved.
Otherwise directly return your Promise.all() and create the map in the calling method.
So something like:
const tasksPromises = [];
taskIds.forEach((taskId) => {
tasksPromises.push(getTask(taskId));
});
return Promise.all(tasksPromises);
Then inside your calling method resolve the promises through then and you will have as parameter of the callback function an array where each element is the returned value of the corresponding promise.
I believe this happening because the taskRateMap isn't being populated before it's being returned. You might want to look into Promise.all()
and consider wrapping
promises = taskIds.map((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
return [taskId, res.task.default_hourly_rate];
console.log(taskRateMap);
}
});
return Promise.all(promises).then(v => /* taskRateMap*/)

Conditional call to second async function, based on data collected before the first async function

I have some data in a array.
let data = ['firstData', 'secondData'];
The first object (firstData) always exists, secondData could be null. So I always use that to call my async function like:
myAsyncFunction(data[0]).then(result => {
//do something here
});
If data[1] exists, I wanna call myAsyncFunction with data[1].
My current solution is with nested promises. Like this:
myAsyncFunction(data[0]).then(result => {
return result;
}).then(result => {
if(data[1] !== null){
myAsyncFunction(data[1].then(resultTwo => {
//dostuff with data
});
}
});
I don't really like this solution, but it does work. It must be a better way tho. result is always a object. I tried Promise.all but it does not work. I think it's a problem with "race condition" of some sort (I am on thin ice here).
Is my current solution the only one?
Here is what myAsuncFunction looks like:
myAsyncFunction(personData){
return new Promise<any> ((resolve, reject) => {
//Assingen private variables
secondAsyncFunction(somePersonData).then(result =>
{
//Do some sync stuff
return someNewData; //not a promise
})
.then(thirdAsyncFunction)
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
Promise.all is designed to be used when you have a dynamic number of promises in an array, and you want to combine them into a promise which resolves when all of the promises in the array have resolved.
According to your use case, you should be able to use it like this:
data = data.filter(x => x !== null);
Promise.all(data.map(myAsyncFunction)).then((results) => {
const [resultOne, resultTwo] = results;
if (resultTwo) {
// handle resultTwo
}
// handle resultOne
});
Given that data is known statically before the promise result, you don't need to put the condition inside the then callback - you can make the whole chaining conditional:
var promise = myAsyncFunction(data[0]);
if(data[1] !== null){
promise.then(result =>
myAsyncFunction(data[1])
).then(resultTwo => {
//dostuff with data
});
}
In general, notice that nesting is not a bad thing, it is required when you want to branch control flow. Just don't forget to always return your promises from all (callback) functions.
You could just simply do:
const stuffOnlyRelatedToSecondData = result2 => /*do something*/
myAsyncFunction(data[0]).then(result => {
if (data[1] === null) {return null}
return myAsyncFunction(data[1]).then(stuffOnlyRelatedToSecondData)
}).then(/*Anything to do after [1] or [1,2]*/)
This way you can use the same chain and the two possible cases will look like:
myAsyncFunction(data[0]) --> END
myAsyncFunction(data[0]) --> myAsyncFunction(data[1]) --> END
Edit:
It seems that myAsyncFunction may have a promise leak, try using something like this instead:
const myAsyncFunction = personData => {
//Assigning private variables
return secondAsyncFunction(somePersonData).then(result => {
//Do some sync stuff
return someNewData; //not a promise
})
.then(thirdAsyncFunction)
}
We can write it this way because secondAsyncFunction kicks off a new Promise, hence we don't need another Promise wrapper around it. And the catch can be put in the parent Promise chain itself which executes myAsyncFunction

How do you wait to return a value until promise is resolved?

I'm trying to write a function that doesn't return it's value until a Promise inside the function has resolved. Here's a simplified example of what I'm trying to do.
'use strict';
function get(db, id) {
let p = db.command('GET', 'group:${id}');
p.then(g => {
g.memberNames = [];
g.members.forEach(id => {
User.get(db, id)
.then(profile => g.memberNames.push(profile.name))
.catch(err => reject(err));
});
return g;
});
}
It's a function that requests a group id and returns the data for that group. Along the way it's also throwing in the user's names into the data structure to display their names instead of their user id. My issue is that this is running asynchronously and will skip over the .then callbacks. By the time it gets to return g none of the callbacks have been called and g.memberNames is still empty. Is there a way to make the function wait to return g until all callbacks have been called?
I've seen a lot of stuff about await. Is this necessary here? It's highly undesired to add other libraries to my project.
Since your operation to return all the profile names is also async you should return a Promise fulfilled when all the other async operations complete (or reject when one of them is rejected) done with Promise.all
function get(db, id) {
let p = db.command('GET', 'group:${id}');
return p.then(g => {
return Promise.all(g.members.map(id => {
// NOTE: id is shadowing the outer function id parameter
return User.get(db, id).then(profile => profile.name)
})
})
}
Is there a way to make the function wait to return g until all callbacks have been called?
Yes, there is. But you should change your way of thinking from "wait until all the callbacks have been called" to "wait until all the promises are fulfilled". And indeed, the Promise.all function trivially does that - it takes an array of promises and returns a new promise that resolves with an array of the results.
In your case, it would be
function get(db, id) {
return db.command('GET', 'group:${id}').then(g => {
var promises = g.members.map(id => {
// ^^^ never use `forEach`
return User.get(db, id).then(profile => profile.name);
// ^^^^^^ a promise for the name of that profile
});
//
return Promise.all(promises).then(names => {
// ^^^^^^^^^^ fulfills with an array of the names
g.memberNames = names;
return g;
});
});
}

Categories

Resources