Promise.all no return res map nodejs - node.js

We are integrating with Outlook api and we need to group the attachments in the recovered emails:
We re trying this way:
const result = await client
.api('/me/messages')
.filter(searchMailFrom)
.select('subject, from, receivedDateTime, sentDateTime, isRead, toRecipients, hasAttachments')
.get()
let dadosAnexo = result.value.map(async item => {
if (item.hasAttachments) {
const resultAtt = await client
.api('/me/messages/' + item.id + '/attachments')
.get()
item.anexos = resultAtt.value
}
})
await Promise.all(dadosAnexo)
return res.status(200).send(result.value)
But when we put Promise.all (), the system simply returns nothing

You are not returning anything from inside your .map functions. So, dadosAnexo becomes an array of Promises that will each resolve to undefined.
Check the MDN docs for more details about how .map works: Map | MDN.
Then, you are passing dadosAnexo to your Promise.all call.
But when we put Promise.all (), the system simply returns nothing
Here, your assumption is wrong.
await Promise.all(dadosAnexo)
The await Promise.all call above will actually return an array of undefined. Because you are passing it dadosAnexo (an array of Promises, each of which resolves to undefined). Also, you are not assigning the return value to any variable (so, you don't actually know if it's returning something or not).
Check the MDN docs for more details about how Promise.all works: Promise.all() | MDN
Now to solve your problem, here's a solution:
const result = await client
.api('/me/messages')
.filter(searchMailFrom)
.select('subject, from, receivedDateTime, sentDateTime, isRead, toRecipients, hasAttachments')
.get()
// promisesToAttach will be an array containing some Promises and some item values
const promisesToAttach = result.value.map(item => {
if (item.hasAttachments) {
// returning a promise
return client
.api('/me/messages/' + item.id + '/attachments')
.get()
.then(resultAtt => {
item.anexos = resultAtt.value
return item
})
}
// returning an item value
return item
})
// resultWithAttachments will be an array of resolved item values
const resultWithAttachments = await Promise.all(promisesToAttach)
return res.status(200).send(resultWithAttachments)

Related

Promise.all object map messes the sorting of data

I have a node api that returns an object but the object returned are not always at the same order. This is how I did it:
const somelist = await Model.find({condition blah blah})
.sort({created_at: -1})
.select('some fields');
if(!somelist){
res.status(500).json({ success: false });
}else{
let arr = [];
await Promise.all(somelist.map(async (item) => {
let user = await User.findById(item.some);
if(user) {
arr.push(item);
}
}));
console.log(arr)
res.status(200).json(arr);
}
The original display would be 1, 2, 3 but sometimes, it will become 2, 1, 3 but very rarely. Since I added a sort function in my somelist, maybe the issue is in the promise. How should I deal with this?
No, neither Promise.all nor map does change the oder of an array. Promise.all is guaranteed to return the results in the same order as the promisearray, you put in.
BUT: the order, in which the promises are resolved isn't guaranteed, thus the arr.push() can happen in any order, so you may receive different results. So you can do it somehow like this
let arr = (await Promise.all(somelist.map(i => User.findById(i.some)))
.filter(r => !!r)
Assuming that User.findById() returns a promise, somelist.map(...) returns an array of promises, which you can await with Promise.all. Once Promise.all has resolved, you can filter out all elements, which returned a undefined or null result.
Another possiblity would be the following
for (let item of somelist) {
let u = await User.findById(item.some);
if (u) arr.push(u);
}
But that is serializing your requests and executing one after the other. So this would probably waste a lot of time waiting for some external results (network, database, disk, ...)
I believe you should use this code:
let arr = await Promise.all(somelist.map(async (item) => {
return await User.findById(item.some);
}));
In your code you push the async function result into an array and this statement not necessarily would work in the specified order. But Promise.all would return resolved results in your original array order.

Angular, extract the value from a promise

I have a function that returns a Promise and inside that Promise I get an object in the resolve.
Here it is the function of my service that works good.
buscarUsuario(email: string){
return new Promise((resolve, reject) => {
this.http.post(`${URL}/user/email`, {email})
.subscribe(resp => {
//console.log(resp['usuario']);
resolve(resp['usuario']);
});
})
}
And then I get the value from the promise in this var:
const getDatos = this.usuarioService.buscarUsuario(this.correoUsuario.value.toString());
And then I call the var to get the value from the resolve and I can't extract that value from there:
var usuario: Usuario;
getDatos.then(usu => {
usuario = usu;
//Here I can see the value
console.log(usuario);
});
//But here I can't see the value
//And it's where I really need to get the value
console.log(usuario);
So, how do I get that value outside the Promise?
Using Promises in Angular is NOT recommended. Angular recommends use of Observable to handle asynchronous operations
Lets Try and change your code to return Observables only
buscarUsuario = (email: string) =>
this.http.post<any>(`${URL}/user/email`, {email}).pipe(
map(resp => resp.usuario as Usuario)
)
Basically the above code returns an Observable<any>(Observable of type any). I have type casted using <any> to transform the result to an Obserable<any>. Next I have use piping to extract the usuario from response
Now we can actually assign this value to a variable...
const getDatos$ = this.usuarioService.buscarUsuario(this.correoUsuario.value.toString());
NOTE: This is an Observable and you will need to subscribe to it
Observable can be assigned like any other property
const usuario: Observable<Usuario> = getDatos$
You cannot get the value outside that function, reason because since you use promise, you need to wait until the promise returns the value, so best possible way is you have do rest of your functionality within promise.then.
getDatos.then(usu => {
//implement your other functionality
});`

Parsing Zip file in TS. Despite await, returned result appears empty while result within function is not

I'm writing some code to parse all CSV files stored inside a Zip folder.
I'm using JSZip for this. I'm looping over all files of a dataset and converting each file into an entry. I'm concatenating the result in an array called entries.
This is what happens to the entries array when:
I log it to console from within the loop - Prints file correctly.
I log it to console from the same function right outside the loop without setTimeout - Prints empty array
I log it to console from the same function right outside the loop WITH setTimeout - Prints file correctly.
Now I returned entries from this function, I tried returning it from the:
setTimeout - returns undefined
Right outside setTimeout - returns empty array
Here is the code:
private async parseAllFiles (dataset: JSZip, id: string): Promise<string[]> {
let entries: string[] = [];
await dataset.forEach( async (fileName) => {
const fileContent = await dataset.file(fileName).async("text"); // async func provided by JSZip
const parsedEntries = await this.parseCsv(fileContent, courseCode); // another async function
});
console.log('Entries are: \n');
console.log(entries); // Prints empty array
setTimeout( () => {
console.log("Entries are: ");
console.log(entries); // prints perfectly
return Promise.resolve(entries); // returns undefined
}, 1500);
// return Promise.resolve(entries); // returns empty array
}
I'm pretty sure entries is getting returned too early, but how do I fix that, preferably without using a manual setTimeout ?
forEach is a void returning function, and thus isn't awaitable.
What you could do is create an array of Promises:
const promises: Promise[] = [];
Then add to the array within your forEach:
dataset.forEach(fileName => {
promises.push(async () => {
const fileContent = await dataset.file(fileName).async("text");
const parsedEntries = await this.parseCsv(fileContent, courseCode);
return parsedEntries;
});
});
Then wait for them all to complete:
const entries: string[] = (await Promise.all(promises)).flat();
Promise.all returns a new Promise, which resolves to an array of the provided Promise results.
These can be flattened into a single array using Array.flat().

combining results from several async/await calls

I want to return the consolidated results from calling many async/await functions. Consider my (wrong) pseudocode below
const getRemoteData = async function(uri) {
// get result from remote server via XMLHttpRequest
return result;
}
const finalResult {};
const result1 = getRemoteData('http://…');
result1.forEach(async record => {
// each record has a key for another URI
const result2 = await getRemoteData(record["uri"]);
// result2 has a key for yet another URI
const result3 = await getRemoteData(result2["uri"]);
finalResult[ result2["uri"] ] = result3;
})
console.log(finalResult);
Of course, since console.log(finalResult) is called before all the interim calls in the forEachloop have completed, finalResult is empty. How do I return finalResult after all the entries in result1 have been processed? I am using the latest version of nodejs and, if possible, would rather not use any other Promise library. From what I understand, the new async/await capabilities should enable me to accomplish what I want.
You need to use .map instead of .forEach in order to .map each item in result1 to a Promise that resolves once the associated result3 has been assigned to finalResult. Then, use Promise.all on that array, which will resolve once all of its Promises have resolved, after which finalResult will be populated with everything you need:
const getRemoteData = function(uri) {
// pretty sure you didn't mean to recursively call getRemoteData:
return someApiCall(uri);
}
const result1 = await getRemoteData('http://…');
const finalResult = {};
await Promise.all(result1.map(async (record) => {
const result2 = await getRemoteData(record.uri);
const result3 = await getRemoteData(result2.uri);
finalResult[result2.uri] = result3;
}));
console.log(finalResult);
(note that instead of an async function that await's something and returns it immediately, you may as well just return the Promise directly instead - also, dot notation is usually preferable to bracket notation, bracket notation is when you need to use a variable property name)

Map Promise.all output with promises index

I am using nodejs v8+ which supports default async await style of code.
In my problem, I am trying to push all the promises into an array and then use Promise.all to with await keyword to get the responses. But the problem is, I am not able to map the promises with the keys.
Here is the example:
let users = ["user1", "user2", "user3"];
let promises = [];
for(let i = 0; i < users.length; i++){
let response = this.myApiHelper.getUsersData(users[i]);
promises.push(response);
}
let allResponses = await Promise.all(promises);
Here I get all the collected response of all the responses, but I actually want to map this by user.
For example currently I am getting data in this format:
[
{
user1 Data from promise 1
},
{
user2 Data from promise 2
},
{
user3 Data from promise 3
}
]
But I want data in this format:
[
{
"user1": Data from promise 1
},
{
"user2": Data from promise 2
},
{
"user3": Data from promise 3
}
]
I am sure there must be a way to map every promise by user, but I am not aware of.
We gonna create an array of user. Iterate over it to create an array of Promise we give to Promise.all. Then iterate on the answer to create an object that's matching the user with the associated answer from getUsersData.
Something to know about Promise.all is that the order of the returned data depends on the order of the promises given in entry of it.
const users = ['user1', 'user2', 'user3'];
const rets = await Promise.all(users.map(x => this.myApiHelper.getUsersData(x)));
const retWithUser = rets.map((x, xi) => ({
user: users[xi],
ret: x,
}));
Here you have a great tutorial about Array methods (map, filter, some...).
Though the answer accepted by me is one of the solution, which I think is perfect and I will keep as accepted answer but I would like to post my solution which I figured out later which is much simpler:
We could simply use this:
let finalData = {};
let users = ["user1", "user2", "user3"];
await Promise.all(users.map(async(eachUser) => {
finalData[user] = await this.myApiHelper.getUsersData(eachUser);
}))
console.log(finalData);
as described here
How to use Promise.all with an object as input
Here is a simple ES2015 function that takes an object with properties that might be promises and returns a promise of that object with resolved properties.
function promisedProperties(object) {
let promisedProperties = [];
const objectKeys = Object.keys(object);
objectKeys.forEach((key) => promisedProperties.push(object[key]));
return Promise.all(promisedProperties)
.then((resolvedValues) => {
return resolvedValues.reduce((resolvedObject, property, index) => {
resolvedObject[objectKeys[index]] = property;
return resolvedObject;
}, object);
});
}
And then you will use it like so:
var promisesObject = {};
users.map((element, index) => object[element] = this.myApiHelper.getUsersData(users[index]));
promisedProperties(object).then(response => console.log(response));
Note that first of all you need an object with key/promise.

Resources