Map Promise.all output with promises index - node.js

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.

Related

NodeJS/MongoDB function returning array with wrong data

so I have a collection where I will have a lot of documents, but for now lets suppose it has about 100. (It had less than that when I was testing).
So, I need to get all the documents of the collection, put in a array, sort that and then send it to the websocket for the frontend. But the array is going with wrong data.
This is my code:
const emitSales = async (socket) => {
let salesArray = [];
const saleExists = (contract) => {
return salesArray.some(element => element.contract === contract);
}
const addSale = (contract) => {
const element = salesArray.find(e => e.contract === contract);
element.sales = element.sales+1;
}
const sales = await fiveMinSchema.find({}).lean();
if(sales) {
for await (x of sales) {
if(saleExists(x.contract)) {
addSale(x.contract);
continue;
}
const collection = await Collection.findOne({
contract: x.contract
});
let newsale = {
contract: x.contract,
title: collection.title,
description: collection.description,
image: collection.image,
sales: 1,
}
salesArray.push(newsale);
}
socket.emit("5min", salesArray.sort((a,b) => {
return b.sales-a.sales;
}).slice(0,10));
}
}
So, when I execute this function only once, for example, the array returns the correct values. But if I execute the function like 2 times in a row (like very fast), it starts returning the array with wrong data. (like mixing the data).
And as I using websocket, this function will execute like every 2 seconds (for example). How can I fix this problem? Like it seems to be executing more than one time simultaneously and mixing the data, idk..

How do I chain a set of functions together using promises and q in node.js?

I have some dynamic data that needs to have work performed on it. The work must happen sequentially. Using the Q Library, I'd like to create an array of functions and execute the code sequentially using sequences. I can't seem to quite figure out the syntax to achieve this.
const fruits = ["apple", "cherry", "blueberry"]
function makeFruitPie (fruit) {
return Q.Promise((resolve, reject) => {
// Do some stuff here
resolve(fruit+" pie")
// Error handling here
reject(new Error(""))
})
}
const fruitFuncs = new Array(fruits.length)
for(var i = 0; i < fruits.length; i++) {
fruitFuncs[i] = makeFruitPie(fruits[i])
}
// Stole this example from the github docs but can't quite get it right.
i = 0
var result = Q(fruits[i++])
fruitFuncs.forEach((f) => {
result = result(fruits[i++]).then(f)
})
With these lines
for(var i = 0; i < fruits.length; i++) {
fruitFuncs[i] = makeFruitPie(fruits[i])
}
you already run the functions and, hence, their processing will begin.
Assuming you want the execution of the functions in sequence, the following would be more appropriate:
// construct the pipeline
const start = Q.defer();
let result = start.promise; // we need something to set the pipeline off
fruits.forEach( (fruit) => {
result = result.then( () => makeFruitPie( fruit ) );
});
// start the pipeline
start.resolve();
Sidenote: There is a native Promise implementation supported by almost all environments. Maybe consider switching from the library backed version.
You can use Promise.all
Promise.all(fruits.map(fruit=>makeFruitPie(fruit).then(res=> res) )).
then(final_res => console.log(final_res))
final_res will give you array of results
you could use for..of and do things sequentially. something like this
const Q = require("q");
const fruits = ["apple", "cherry", "blueberry"];
function makeFruitPie(fruit) {
return Q.Promise((resolve, reject) => {
// Do some stuff here
resolve(`${fruit} pie`);
// Error handling here
reject(new Error(""));
});
}
for (const fruit of fruits) {
const result = await makeFruitPie(fruit);
console.log(result);
}
By the way also worth considering native Promise insteead of using q

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
});
}

ES6 - make multiple requests for multiple user accounts in parallel

I am building an express.js web application, and for one of the API requests I need to make multiple requests for multiple user accounts in parallel and return one object.
I tried using generators and Promise.all but I have 2 problems:
I don't run in parallel for all user accounts.
My code ends after the response has already returned.
Here is the code I wrote:
function getAccountsDetails(req, res) {
let accounts = [ '1234567890', '7856239487'];
let response = { accounts: [] };
_.forEach(accounts, Promise.coroutine(function *(accountId) {
let [ firstResponse, secondResponse, thirdResponse ] = yield Promise.all([
firstRequest(accountId),
secondRequest(accountId),
thirdRequest(accountId)
]);
let userObject = Object.assign(
{},
firstResponse,
secondResponse,
thirdResponse
);
response.accounts.push(userObject);
}));
res.json(response);
}
_.forEach is not aware of Promise.coroutine and it is not using the return values.
Since you're already using bluebird, you can use its promises aware helpers instead:
function getAccountsDetails(req, res) {
let accounts = [ '1234567890', '7856239487'];
let response = { accounts: [] };
return Promise.map(accounts, (account) => Promise.props({ // wait for object
firstResponse: firstRequest(accountId),
secondResponse: secondRequest(accountId),
thirdResponse: thirdRespones(accountId)
})).tap(r => res.json(r); // it's useful to still return the promise
}
And that should be the code in its entirety.
Coroutines are great, but they're useful for synchronizing asynchronous stuff - in your case you actually do want the concurrency features.

Named promise results with q.all in NodeJS

I'm kinda new to this q stuff and I find it pretty awesome, but there's something I still can't figure out.
I managed to run some combined promises with q.all by passing q.all an array of promises. Something like this..
var promises = [promiseOne(), promiseTwo()];
q.all(promises).then(function (results) {
res.send(results);
} );
The thing with this is that I would actually want those promises to be named, so I don't have to rely in the order of the promises.
I read somewhere that you can actually pass an object to q.all, to have the results named. So that would be something like this:
var promises = { promiseOne: promiseOne(), promiseTwo: promiseTwo() }
q.all(promises).then(function(results){
res.send(results);
});
But I guess this just doesn't work the same way as sending an array as I'm not getting the results of my promises in there. The result I get is similar to this one:
{
promiseOne: {
source: {}
},
promiseTwo: {
source: {}
}
}
So how would you go about getting named results from q.all?
One thing to note is that the amount of promises I will have in the promises array is not fixed as I get that from a GET param sent by the user to my function.
Also, inside each of my promises I have another array (or object) of promises to be resolved and whose results I would like to be named as well.
Here is another way to write the code Roamer wrote with the functionality you asked for (return an object):
Q.props = obj => {
const ps = Q.all(Object.keys(obj).map(x => Q.all([x, obj[x]])));
return ps.then(x => x.reduce((p, c) => {p[c[0]] = c[1]; return p } , {}));
};
Which would let you do:
Q.props(promises).then(o => {
o.promiseOne
});
Although you should consider using bluebird if you want all these helper functions.
Q.js appears not to offer Q.all(object), therefore you will need to map your object to an array before passing to Q.all()
Something like this will be reusable and convenient :
Q.allObj = function (obj) {
var array = Object.keys(obj).map(function(key, i) {
try {
//expect a promise
return obj[key].then(function(value) {
return {key: key, value: value};
});
}
catch(e) {
// whoops it was a value
return {key: key, value: obj[key]};
}
});
return Q.all(array);
};
Use as follows :
Q.allObj(myObj).then(function(results) {
results.forEach(function(obj) {
var name = obj.key;
var value = obj.value;
...
});
});

Resources