combining results from several async/await calls - node.js

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)

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.

Promise.all no return res map nodejs

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)

What is a good design pattern for awaiting Promise.all with extra data

I would like some guidance on a good way to pass data along with an array of promises so that the data can be used later after calling await Promise.all().
I'm pretty new to node and promises. The code below, in summary, does what I want, but it seems messy to have the promises array separate from the corresponding data array. I would think this is a common occurrence and would have a simpler solution.
I was able to accomplish the same result by attaching a then() function to the asyncFunc return, but this makes the code messier and for some reason takes twice as long to execute.
async function foo(inputs) {
var promises = [];
var datas = [];
for (var input of inputs.values()) {
promises.push(asyncFunc(input.a));
datas.push(input.b);
}
var outputs = [];
for (let result of (await Promise.all(promises)).values()) {
outputs.push({
data: datas.shift(),
result: result
});
}
return outputs;
}
You will often face this kind of problem in nodeJs, the answer is how you want your async function react.
If you want it run sequentially, you don't need promise all. The code will look like this.
async function foo(inputs) {
const results = [];
const datas = [];
for (const input of inputs.values()) {
const result = await asyncFunc(input.a);
results.push(result);
datas.push(input.b);
}
const outputs = [];
for (const result of results) {
outputs.push({
data: datas.shift(),
result: result
});
}
return outputs;
}
In case you have to use the promise all, you can use another variable to hold the values, this will make the code less messy.
const results = await Promise.all(promises)).values();
for (const result of results) {
outputs.push({
data: datas.shift(),
result: result
});
}

Promise.all and to be executed only during the all (==lazily)

I am trying to better understand how to run Promise.all that will execute the relevant methods only during the "all"
In this specific flow, the methods are being executed immediately.
what am i missing?
var p1 = p.connectToServer(platform1, username1, password1)
var p2 = p.connectToServer(platform2, username2, password2)
var p3 = p.connectToServer(platform3, username3, password3)
//some logic to decide what to show and what to filter out
//it might not make sense in this snippet, but in the full code-base
//this seperation is important
var params = [p3,p2]
Promise.all(params)
.then((responses) => {
console.log("--- value ---")
console.log(responses.length)
}
P.S. In my case, there is one place preparing the functions for a later stage, and some other logic happening in parallel, that helps to understand what methods shouldn't run and should be filtered out (before the execution)
The link in comment by Antonio Narkevich is reasonable, but from my point of view it is kind of overengineered solution.
The answer is basically "no". No, you cannot control the flow of Promise resolving. Once the Promise is created, you have no control over "when and how" it is being resolved.
The Promise.all is just waiting after all the Promises are resolved and then it returns their values.
If you need something to create promise more dynamically, the easiest way is to make some kind of factory method.
const giveMePromise = () => Promise.resolve(10);
// some code ...
giveMePromise().then()
If you can use async/await, it can be used very easily (you can also put all promises inside array and then await the Promise.all) :
async () => {
var p1 = () => p.connectToServer(platform1, username1, password1)
var p2 = () => p.connectToServer(platform2, username2, password2)
var p3 = () => p.connectToServer(platform3, username3, password3)
const responses = [];
var params = [p1,p2,p3]
for (let i=0; i < params.length; i++){
const response = await params[i]();
responses.push(responses);
}
console.log("--- value ---")
console.log(responses.length)
}
On the other hand, be very careful with "functions inside functions" when using async/await as it is not working as you would probably expect. In this case - using params.forEach() would not work.

About the local variable in Node.js promise

I am a newer of Node.js.I defined a array as a local variable,and want to use it in the following then,I save some useful data in it.But in the end, the array is empty.Can somebody tell me why?Thanks for your support.
const Device = require("./mongo.js").Device;
const Video = require("./mongo.js").Video;
Device.findOne({id:"11112222"}).exec()
.then(function(data){
var videoIds = data.videoIds.split(",");
var videoId2URL = [];
console.log(videoIds);
videoIds.forEach(function(one){
return Video.findOne({id:one}).exec()
.then(function(data){
videoId2URL.push({id:one,url:data.url});
return videoId2URL;
})
});
console.log(videoId2URL);
});
The problem is that you are displaying videoId2URL too early.
Device.findOne returns a promise executed asynchronously. But Video.findOne also returns a promise executed asynchronously.
So when you do console.log(videoId2URL);, the promises created by Video.findOne are not executed yet. So your array is empty.
You must wait the end of all your promises. You can use Promise.all for that.
Promise.all(videoIds.map(function(one){
return Video.findOne({id:one}).exec()
.then(function(data){
videoId2URL.push({id:one,url:data.url});
return videoId2URL;
});
})
.then(function() {
console.log(videoId2URL);
});
You could use Promise.all to resolve your problem. You forEach code contains async code. Your last line does not wait for all promises to get resolved.
Try with:
var arr = [];
videoIds.forEach(function(one){
return arr.push(Video.findOne({id:one}).exec());
});
Promise.all(arr) // here we are waiting for all async tasks to get resolved
.then(function(data){
console.log(data);
// parse your data here and find array of videoId2URL
})
When you do console.log(videoId2URL), you're still in the main stack for the script, while none of the push callbacks have been executed.
You can use an array to collect the promises returned by Video.findOne, and at the end use Promise.all to drain all the promises and do the log then.
BTW, none of the 2 return are necessary, you can safely remove them.
The 1st one is not used because it's in a synchronous callback for forEach.
The 2nd one is not used because you're relying on the side effect, rather than use the resolved value.
Try:
const Device = require("./mongo.js").Device;
const Video = require("./mongo.js").Video;
Device.findOne({id:"11112222"}).exec()
.then(function(data){
var videoIds = data.videoIds.split(",");
var videoId2URL = [];
var promiseArr = [];
console.log(videoIds);
videoIds.forEach(function(one){
var p = Video.findOne({id:one}).exec()
.then(function(data){
videoId2URL.push({id:one,url:data.url});
});
promiseArr.push(p);
});
Promise.all(promiseArr).then(function() {
console.log(videoId2URL);
});
});

Resources