Resolve a promise nested inside an array of objects - javascript

I have an array of objects that I get from database.
const users = await User.findAll();
To each of them, I want to call an async function user.getDependency() to get another related table, and devolve it nested in the object to a final format like this:
[
User {
attr1: value,
attr2: value,
...
Dependency: {
attr1: value,
...
}
},
User {
...
and so on
Now, I get my problem. The only possibilities I am being able to think involving async and loops are 2:
Option 1: mapping them and getting the promise back:
users.forEach((user) => {
user.dependency= user.getDependency();
});
With this I get similar to the desired outcome in terms of format, only that instead of the resolved thing i get obviously Dependency: Promise { <pending> }, that is, the promises all nested inside each user object of the array. And I don't know how to procceed with it. How to loop through all the user objects and resolve this promises they have inside?
Option 2: return the promises to be resolved in Promise.all
const dependencies = Promise.all(users.map((user) => {
return user.getDependency();
}));
This give me a separated array with all the dependecies I want, but that's it, separated. I get one array with the objects users and another with the objects dependencies.
What is bugging me specially is that I am thinking that there must be a simple straightforward way to do it and I am missing. Anyone have an idea? Much thanks

To build some intuition, here is your forEach example converted with a push in the right direction.
users.forEach((user) => {
user.getDependency().then(dependency => {
user.dependency = dependency
})
})
The above reads "for each user, start off the dependency fetching, then add the dependency to the user once resolved." I don't imagine the above is very useful, however, as you would probably like to be able to do something with the users once all the dependencies have finished fetching. In such a case like that, you could use Promise.all along side .map
const usersWithResolvedDependencies = await Promise.all(
users.map(user => user.getDependency().then(dependency => {
user.dependency = dependency
return user
}))
)
// do stuff with users
I should mention I wrote a library that simplifies async, and could certainly work to simplify this issue as well. The following is equivalent to the above. map and assign are from my library, rubico
const usersWithResolvedDependencies = map(assign({
dependency: user => user.getDependency()
}))(users)

I guess the best way is to do like this. Mapping users, updating every object (better to do this without mutation), and using Promise.all
const pupulatedUsers = await Promise.all(users.map(async (user) => ({
...user,
dependency: await getDependency(user.dependency_id)
// or something like getDependency
// function that fires async operation to get needed data
}));

you're right there is a straightforward way to resolve the promise
as the users variable is not a promise waiting to be resolved, you should try
users.then((userList) => {
// function logic here
})
the userList in the then function should be iterable and contain the desired user list
also you can also handle the rejected part of the promise, if at all the query fails and the promise returns an error:
users.then((error) => {
// error handling
})
you should give this document a glance to get more info about handling promises to further know what goes on in that query when it returns a promise

Related

Promise returns undefined value from mapping

I'm trying to work with data sent from postman, array of objects.
I need to write all data into database, so I use mapping of array, and get undefined
const providers = await req.body.providers.map((provider) => {
ProviderService.createProvider(provider.name, container._id);
});
Promise.all(providers).then((value) => {
console.log(value);
});
I get from console log array of undefined, but items are creating in Data Base, I know I have mistake in asynchronous functions, but I don't really understand - where
Thank you for answer
if I got this right, when you use the await keyword inside async function you actually wait for the promise to be resolved then you assign its extracted value to the providers variable.
Promise.all takes as parameter an array of promises, so I guess this is where things go wrong when you send it an array with values.
Your first statement is returning an Array. You can only use await on promises.
Promise.all() does return a promise though.
So you just need to move the await to the correct place.
const providers = req.body.providers.map((provider) => {
ProviderService.createProvider(provider.name, container._id);
});
await Promise.all(providers).then((value) => {
console.log(value);
});

What's the point of returning something from an Async/Await Function?

I've created a code snippet here that fetches data about a certain country using REST Countries API. The function works fine, and returns a pending promise. That promise's fulfilled value will equal an object containing key value pairs for the country's capital, name, and code.
But if I wanted to use them for something, why wouldn't I just set that very same object equal to a variable and continue to program new actions inside my async function? Why bother trying to use values gained asynchronously on the global scope?
function getJSON(url, errorMSG = 'Something went wrong') {
// feed it the fetch URL, then your customized error message
return fetch(url).then(response => {
if (!response.ok) throw new Error(errorMSG);
return response.json();
});
}
async function countryData(nation) {
try {
const info = await getJSON(
`https://restcountries.eu/rest/v2/name/${nation}?fullText=true`,
'Invalid country selected'
);
return {
// Fullfilled value of promise
capital: info[0].capital,
name: info[0].name,
code: info[0].cioc,
};
} catch (err) {
console.error(err); // display your custom error message
}
}
console.log(countryData('Canada'));
fetch is an async function. Why do they resolve the promise to a response object, instead of continuing to run extra actions once they have it? Because they don't know what you want to do with it. It would be impossible for fetch to handle every possible thing that should happen next, so it just has a single job: get the data, then return it (in a promise). You can then combine this with whatever other code you like.
On a smaller scale, this may happen with countryData too. You might have 10 different parts of your app that want to do things with the result from countryData. It may not be "impossible" for countryData to implement all 10 things, but it's definitely impractical and not a good idea. Instead, countryData can be written to have one job: get the country data and return it. Then each of the 10 pieces of code can do their own things with the result.
This isn't about it being async, the same principles apply to synchronous code to. If you can keep code focused on a single task, without entangling it with the needs of other pieces of code, then your code becomes easier to maintain.

Struggling to get my promises to work in an array loop

I have been struggling with this issue for a week and have researched myself close to death. I am a total newbie. I have managed to grasp the crux of promises, but I am failing to see how to include this in a loop.
I have an app that is looking through an array. There is some validation of the array against a mongoose database (which is taking time to run). I am trying to push items into a new array based on some of this validation. I know the validation is working because of the console log in the loop. However my final array is not waiting for the loop to finish. Which means I need to put the loop into a promise, or well I think, but the issue is that I don't know how to resolve it. Current output is a blank array instead of the validated array. Here is my code:
//dummy data of an array - this is originally extracted from a mongoose DB and works (it's my first promise).
const appArray = ["5f8f25d554f1e43f3089ea5d",
"5f8f25e854f1e43f3089ea5e",
"5f8f25f454f1e43f3089ea5f",
"5f8f314ab92c7f406f28b83a",
"5f8fe50a9d44694cad91a01b",
"5f92e8a75d848870e015dff3",
"5f92e8b35d848870e015dff4",
"5f92e8cb5d848870e015dff5",
"5f8fe51d9d44694cad91a01c"];
//the second promise takes the array above and validates it against another collection on mongoose
function myPromise2 (response){
return new Promise((resolve, reject) => {
let appoints = [];
response.forEach(e => {
//loop through each item of the array and look against Appointment collection
Appointment.findById(e, function(err, foundApp){
//some validation supposed to happen here and then pushed into a new array
appoints.push(foundApp);
console.log(appoints);
})
})
//once completed supposed to resolve and return
resolve(appoints);
})
};
myPromise2(appArray).then((response) => {console.log(response)});
Here is an example which should work. Add a promise for each element to the array and then resolve the outer function if all promises have resolved.
// dummy data of an array - this is originally extracted from a mongoose DB and works (it's my first promise).
const appArray = ["5f8f25d554f1e43f3089ea5d",
"5f8f25e854f1e43f3089ea5e",
"5f8f25f454f1e43f3089ea5f",
"5f8f314ab92c7f406f28b83a",
"5f8fe50a9d44694cad91a01b",
"5f92e8a75d848870e015dff3",
"5f92e8b35d848870e015dff4",
"5f92e8cb5d848870e015dff5",
"5f8fe51d9d44694cad91a01c"];
// the second promise takes the array above and validates it against another collection on mongoose
function myPromise2 (response) {
let appoints = [];
response.forEach(e => {
appoints.push(new Promise((resolve) => {
//loop through each item of the array and look against Appointment collection
Appointment.findById(e, function(err, foundApp) {
//some validation supposed to happen here and then pushed into a new array
resolve(foundApp);
})
}))
})
return Promise.all(appoints)
};
myPromise2(appArray).then((response) => {console.log(response)});
Points to address:
Use the promise that mongoose provides through the .exec() method. This way you don't need new Promise
Collect these individual promises in an array (use .map instead of .forEach), and pass this array to Promise.all
If you do this, the code for myPromise2 reduces to the following:
function myPromise2 (response){
return Promise.all(response.map(e => Appointment.findById(e).exec()));
}
here is my suggestion:
const appArray = [
"5f8f25d554f1e43f3089ea5d",
"5f8f25e854f1e43f3089ea5e",
"5f8f25f454f1e43f3089ea5f",
"5f8f314ab92c7f406f28b83a",
"5f8fe50a9d44694cad91a01b",
"5f92e8a75d848870e015dff3",
"5f92e8b35d848870e015dff4",
"5f92e8cb5d848870e015dff5",
"5f8fe51d9d44694cad91a01c"
];
function myPromise2 (response){
return Promise.all(response.map(id) => {
return Appointment.findById(id).exec();
})
};
myPromise2(appArray)
.then(console.log) // response array
.catch(// handle errors)
// you can also async/await the calling part
you can also use one of:
Promise.allSettled
Prmoise.any (es2021)
Promise.race
it's just depends on how you would like to handle the responses/failures.
A good alternative to consider maybe Async/Await and have a look Async_await. This will hopefully answer all your issues
It's probably a good idea to look into how the JS event loop system works guide here,

Async findOne() operation inside forEach Loop

I am having a hard time understanding JavaScript Promises. I am searching on of my Mongoose models for objects that meet a certain condition and if they exist, I want to make the object into a plain JS object and add a property onto it.
Unfortunately, I am unable to wrap my head around how I can ensure my forEach loop will run completely before my promise ends up resolving. Please see my code.
// Called to check whether a user has participated in a given list of challenges
participationSchema.statics.getParticipation = function(user, challenges) {
return new Promise((resolve, reject) => {
challengesArray = [];
challenges.forEach((challenge) => {
// Model#findOne() is Async--how to ensure all these complete before promise is resolved?
Participation.findOne({user, challenge})
.then((res) => {
if (res) {
var leanObj = challenge.toObject();
leanObj.participation = true;
challengesArray.push(leanObj);
}
})
.catch(e => reject(e));
})
console.log("CHALLENGES ARRAY", challengesArray); // Challenges Array empty :(
resolve(challengesArray);
});
}
I've looked through similar questions, but am unable to get to an answer. Appreciate the help.
So, what is happening when you call getParticipation is that the forEach loop runs all the way and all individual promises for Participation.findOne are created but not yet resolved. The execution doesn't wait for them to resolve and continues after the forEach, resolving the top-level promise challengesArray, which is still empty at that point. Sometime after, the promises created in the forEach start resolving but their results are now lost.
Also, as Bergi mentioned in the comments, nesting promises is considered an anti-pattern; promises should be chained, not nested.
What you want is to use something like Promise.all to wait for all of your promises to finish first, then you filter out all non-existing results and finally return the array.
participationSchema.statics.getParticipation = function(user, challenges) {
return Promise.all(challenges.map(challenge => {
return Participation.findOne({user, challenge}).then(result => {
if (result) {
var leanObj = challenge.toObject();
leanObj.participation = true;
return leanObj;
}
});
})
// at this point, results contains an array of `leanObject` and `undefined` depending if the `findOne` call returned anything and the code withing the `if` above was run
.then((results) => {
return results.filter(result => !!result) // filter out `undefined` results so we only end up with lean objects
});
}

Error on Bluebird spread function

When I start so code
import Promise from 'bluebird';
const mongodb = Promise.promisifyAll(require('mongodb'));
const MongoClient = mongodb.MongoClient;
MongoClient.connect(url).then((db) => {
return Promise.all([new WorkerService(db)]);
}).spread((workerService) => (
Promise.all([new WorkerRouter(workerService)])
)).spread((workerRouter) => {
app.use('/worker', workerRouter);
}).then(() => {
httpServer.start(config.get('server.port'));
}).catch((err) => {
console.log(err);
httpServer.finish();
});
I see so error
}).spread(function (workerService) {
^
TypeError: MongoClient.connect(...).then(...).spread is not a function
Please help me. What am I doing wrong?
I see several things wrong here:
The root cause here is that MongoClient.connect(...).then(...) is not returning a Bluebird promise, thus, there is no .spread() method. When you promisify an interface with Bluebird, it does NOT change the existing methods at all. Instead, it adds new methods with the "Async" suffix on it.
So, unless you've somehow told Mongo to create only Bluebird promises, then
`MongoClient.connect(...).then(...)`
will just be returning whatever kind of promise Mongo has built in. You will need to use the new promisified methods:
MongoClient.connectAsync(...).then(...).spread(...)
Some other issues I see in your code:
1) Promise.all() expects you to pass it an array of promises. But when you do this: Promise.all([new WorkerRouter(workerService)]), you're passing it an array of a single object which unless that object is a promise itself, is just wrong.
2) In this code:
}).spread((workerService) => (
Promise.all([new WorkerRouter(workerService)])
))
you need to return the resulting promise in order to link it into the chain:
}).spread((workerService) => (
return Promise.all([new WorkerRouter(workerService)])
))
3) But, there's no reason to use Promise.all() on a single element array. If it was a promise, then just return it.
4) It also looks like you're trying to use promises for different steps of synchronous code too. Besides just complicating the heck out of things, there's just no reason to do that as this:
}).spread((workerService) => (
Promise.all([new WorkerRouter(workerService)])
)).spread((workerRouter) => {
app.use('/worker', workerRouter);
}).then(() => {
httpServer.start(config.get('server.port'));
})...
can be combined to this:
)).spread((workerService) => {
app.use('/worker', new WorkerRouter(workerService));
httpServer.start(config.get('server.port'));
})...
And, can probably be condensed even further since new WorkerService(db) probably also doesn't return a promise.
5) And, then we can't really tell why you're even attempting to use .spread() in the first place. It is useful only when you have an array of results that you want to turn into multiple named arguments, but you don't need to have an array of results (since there was no reason to use Promise.all() with a single item and in modern version of node.js, there's really no reason to use .spread() any more because you can use ES6 destructing of an array function argument to accomplish the same thing with the regular .then().
I don't claim to follow exactly what you're trying to do with every line, but you may be able to do this:
import Promise from 'bluebird';
const mongodb = Promise.promisifyAll(require('mongodb'));
const MongoClient = mongodb.MongoClient;
MongoClient.connectAsync(url).then((db) => {
let service = new WorkerService(db);
app.use('/worker', new WorkerRouter(service));
// since httpServer.start() is async and there's no promise returning here,
// this promise chain will not wait for the server to start
httpServer.start(config.get('server.port'));
}).catch((err) => {
console.log(err);
httpServer.finish();
});

Categories

Resources