Promise.all convert result with parameter from nested loop - javascript

The following loop to call an async function, here a smart contract interaction using web3. I want to get the balance of an array of token by calling balanceOf() and convert it subsequently with the attached usdrate. For parallel processing I am using Promise.all. Obviously, the function below Promise.all() with access [I % currency.length] does not work, since sorted result is not guaranteed.
My question is, how can I multiply the amounts with the correct usdrates attached to the tokens and still use Promise.all?
currencies = [{
contract: token1,
usdrate: 0.5
},
{
contract: token2,
usdrate: 1.0
},
{
contract: token3,
usdrate: 1.05
},
{
contract: token4,
usdrate: 1.10
},
{
contract: token5,
usdrate: 1.40
},
{
contract: token6,
usdrate: 1.0
},
{
contract: token7,
usdrate: 1.0
}
];
}
async function getUsdWealthAsync(addresses) {
var totalWealth = 0;
var amountPromises = [];
for (var j = 0; j < currencies.length; j++) {
for (var i = 0; i < addresses.length; i++) {
amountPromises.push(currencies[j].contract.methods.balanceOf(addresses[i]).call());
}
}
await Promise.all(amountPromises).then(function(amounts) {
for (var i = 0; i < amounts.length; i++) {
amounts[i] = Number.parseInt(amounts[i]);
totalWealth += (amounts[i] / 100) * currencies[i % currencies.length].usdrate;
}
})
return totalWealth;
}

async functions always return a Promise, you can define an async function that receives an address and a currency and returns a Promise that has the calculation done already, so you don't have index troubles. Something like
async function getAmount(currency, address) {
const amount = await currency.contract.methods.balanceOf(address).call();
return amount * currency.usdrate;
}
async function getUsdWealthAsync(addresses) {
const amountPromises = [];
for (const currency of currencies) {
for (const address of addresses) {
amountPromises.push(getAmount(currency,address)/*Remember, calling this funciton returns a Promise*/);
}
}
const realAmounts = await Promise.all(amountPromises)
return realAmounts.reduce((total,current) => total+current, 0);
}
Where the last line with the reduce call is supposed to sum all the amounts you have

Why not use a nested Promise.all() to bundle all of the asynchronous calls for a particular currency under a single Promise? By doing this, you also retain the index alignment for processing the response.
async function getUsdWealthAsync(addresses) {
let totalWealth = 0;
let amountPromises = [];
// For each of the currencies...
for (var j = 0; j < currencies.length; j++) {
// Create a set that will hold balance promises for this currency.
const balancePromisesForCurrency = [];
for (var i = 0; i < addresses.length; i++) {
// Create those promises and add them to the set.
balancePromisesForCurrency.push(
currencies[j].contract.methods.balanceOf(addresses[i]).call()
);
}
// Create a new promise that resolves to the list of balance results, ​
// index-aligned to the addresses, for this currency. Add that Promise
// to the set of per-currency Promises, index-aligned to the currencies
// array.
amountPromises.push(Promise.all(balancePromisesForCurrency));
}
// Create a new cumulative promise from the `amountPromises` array.
await Promise.all(amountPromises).then(function (amountsForCurrency) {
// For each of the balance lists received...
amountsForCurrency.forEach((amounts, amountsIndex) => {
// Get the corresponding currency.
const currency = currencies[amountIndex];
// Total up the balances scaled by the currency's USD rate.
amounts.forEach((amount, idx) => {
totalWealth += (+amount / 100) * currency.usdrate;
});
});
})
return totalWealth;
}```

You have other great answers.
Another way could be that, you can attach the USD rate along with the result from the balanceOf in the promise itself, And then while resolving the promises, you can access the USD rate directly.
Maybe something like this:
async function getUsdWealthAsync(addresses) {
var totalWealth = 0;
var amountPromises = [];
for (var j = 0; j < currencies.length; j++) {
for (var i = 0; i < addresses.length; i++) {
const { usdrate, contract } = currencies[j];
amountPromises.push(
contract.methods.balanceOf(addresses[i]).call()
.then((amount) => ({ amount, usdrate }))
);
}
}
const amounts = await Promise.all(amountPromises);
for (var i = 0; i < amounts.length; i++) {
const { amount, usdrate } = amounts[i];
amount = Number.parseInt(amount);
totalWealth += (amount / 100) * usdrate;
}
return totalWealth;
}

Related

Memory difference between array of callbacks with array args and array of args with looped callback

The title is a little confusing... To explain whats happening, I am creating a grid made of custom <Tile />s. For right now lets say grid[y][x] = 1 means a dark tile and grid[y][x] = 0 mean white tile.
I want to create a single function that I can edit to do different things with said grid.
Examples: loop through each tile from (0,0) to (arr.length, arr[0].length), animate borders then insides, etc. I want to be able to create that animation order in a separate function.
Here's what my current code looks like:
const doStuff = (setGameboard, size /*grid is size x size square*/) => {
// Returns a Promise that resolves after "ms" Milliseconds
const timer = (ms) => new Promise((res) => setTimeout(res, ms));
// Create an array of callbacks with each being a step in the gameboard animation
const renderArr = [];
async function renderBoard() {
//Loops over each index of renderArr and calls the function at i-th index
for (var i = 0; i < renderArr.length; i++) {
renderArr[i]();
await timer(5); // wait milliseconds
}
}
const createRenderSteps = (setGameboard, size) => {
for (var i = 0; i < size * size; i++) {
//Get x and y based on I index
const y = Math.floor(i / size);
const x = i % size;
// Add render step (keyframe) to render array
renderArr.push(() => {
setGameboard((gameboard) => {
let temp = [...gameboard];
temp[y][x] = 1;
return temp;
});
});
}
};
createRenderSteps(setGameboard, size);
renderBoard();
};
module.exports.doStuff = doStuff;
To explain it simply, I am currently creating an array of callbacks that represent each "key frame" (i use the terms loosely here) of the animation. for example
renderArr[0] would look like:
() => {
setGameboard((gameboard) => {
let temp = [...gameboard];
temp[0][0] = 1;
return temp;
});
}
My question then comes down to performance.
Would it be "better" to keep this array of callbacks OR to create an array that ONLY contains gameBoards then create a for loop that calls the setGameboard function with the [i]th gameboard? *** looking something like this:
const doStuff = (initialGameboard, setGameboard, size /*grid is size x size square*/) => {
// Returns a Promise that resolves after "ms" Milliseconds
const timer = (ms) => new Promise((res) => setTimeout(res, ms));
// Create an array of gameboards set first index to the initial (unchanged) gameboard
const steps = [initialGameboard];
async function renderBoard() {
//Loops over each index of renderArr and calls the function at i-th index
for (var i = 0; i < renderArr.length; i++) {
setGameboard(steps[i]);
await timer(5); // wait milliseconds
}
}
const createRenderSteps = (setGameboard, size) => {
for (var i = 0; i < size * size; i++) {
//Get x and y based on I index
const y = Math.floor(i / size);
const x = i % size;
const newGameboard = [steps[i]]
newGameboard[y][x] = 1
steps.push(newGameboard);
}
};
createRenderSteps(setGameboard, size);
renderBoard();
};
module.exports.doStuff = doStuff;
EDIT: MY initial thoughts were that the second option (array of gameBoards with looped function call) would be more performant in terms of memory because the array of callbacks STILL has to store a copy of the gameboard along with the rest of the function call but I don't know if that's correct or not

Array is empty after a foreach loop (async/await)

I'm trying to retrieve an array of cards for a project. However, in my function, the final contacts array returns an empty array.
I know that, because I have an async call to another funcion inside the forEach loop, the loop doesn't execute as intended. However, I'm very newbie when it comes to deal with this issues, so I want to ask you what's the best approach to deal with this.
This is my code:
export const extractsIDSForUser = async (currentUser: User) : Promise <Object> => {
let contactCards = currentUser.contacts;
const contacts = [];
const usersRef = await firebase.firestore().collection('Users').get();
const usersSnapshot = usersRef.docs.map(doc => doc.data());
contactCards.forEach(async folder => {
const ids = [];
folder.forEach(contact => {
ids.push(contact);
});
for (let i = 0; i < ids.length; i +=1) {
const contact = ids[i];
for (let j = 0; j < usersSnapshot.length; j += 1) {
const userId = usersSnapshot[j].id;
// Async call to function
const cardsFromUser = await extractCardsFromUser(userId);
const arrayCards = Object.values(cardsFromUser);
if (arrayCards.length > 0) {
for (let j = 0; j < arrayCards.length; j += 1) {
const arrayId = arrayCards[j].id;
const sameCardId = arrayId === contact;
if (sameCardId) {
// Where I insert the values into the array
contacts.push(arrayCards[j]);
}
}
}
}
}
});
// this is empty
return contacts;
}
What will be the best approach to deal with this?
I think you have already found a solution, but I had a similar problem and found this article quite helpful.
You could use a traditional for (const contactCard of contactCards) and it will work, but it will be less efficient than using a Promise.all approach.

Running forEach on Object.entries does not return the same thing as a for loop

I am iterating over an object using a regular for loop and that works fine for me. But, I was trying to remove all for loops of my code in favor of array iteration instead and, for some reason I can't understand why when using forEach I get a different result.
Note: forEach here is from a module called p-iteration
https://www.npmjs.com/package/p-iteration
This works fine, it returns the correct values.
for await (const [key, value] of Object.entries(tatGroupedByRegion)) {
onTarget = 0;
notOnTarget = 0;
const cases = [];
await forEach(value, async email => {
if (!cases.includes(email.new_name)) {
cases.push(email.new_name);
isOnTarget(email);
}
});
backlogData[key].tatd1 = percentage(onTarget, notOnTarget);
tatd1Total.value += parseInt(percentage(onTarget, notOnTarget), 10);
if ((parseInt(percentage(onTarget, notOnTarget) !== 0), 10)) {
tatd1Total.count += 1;
}
}
This does not work,this part here backlogData[key].tatd1 = percentage(onTarget, notOnTarget), returns the same value over and over.
await forEach(Object.entries(tatGroupedByRegion), async ([key, value]) => {
onTarget = 0;
notOnTarget = 0;
const cases = [];
await forEach(value, async email => {
if (!cases.includes(email.new_name)) {
cases.push(email.new_name);
isOnTarget(email);
}
});
backlogData[key].tatd1 = percentage(onTarget, notOnTarget);
tatd1Total.value += parseInt(percentage(onTarget, notOnTarget), 10);
if ((parseInt(percentage(onTarget, notOnTarget) !== 0), 10)) {
tatd1Total.count += 1;
}
});
exports.forEach = async (array, callback, thisArg) => {
const promiseArray = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
const p = Promise.resolve(array[i]).then((currentValue) => {
return callback.call(thisArg || this, currentValue, i, array);
});
promiseArray.push(p);
}
}
await Promise.all(promiseArray);
};
This is the implementation of forEach that you're using. The callback receives this as the first argument, this can be a problem.

Convert generator to normal function

I am working on a project and there is some refactor to do. For internal decision we do not want to use generators and I came across this code (which it looks weird to me because it seems that there is no need for a generator at all). How would I go to convert it to a normal function (I don't think there is any async operation as far as I can tell)?
Just to make clear I do not want to use generators in this code.
Code:
const getResults = (totalData) => function* getNext() {
const combinations = totalData.reduce((a, b) => a * b.length, 1)
for (let i = 0; i < combinations; i++) {
yield createSolution(i, totalData)
}
return null
}
This is how is being called:
const result = getResults(obj.elementsInObj);
for (let data of result()) {
const resolve = validateData(data, obj.elementsInObj)
if (resolve) {
return resolve
}
}
Well, you can remove the asterisk and yield operator and create an internal array to store the solutions, then you can return that array and loop over it.
const getResults = (totalData) => {
const combinations = totalData.reduce((a, b) => a * b.length, 1),
arr = [];
for (let i = 0; i < combinations; i++) arr.push(createSolution(i, totalData));
return arr;
}
const results = getResults(obj.elementsInObj);
for (let data of results) {
const resolve = validateData(data, obj.elementsInObj)
if (resolve) return resolve
}

Resolving Promise.all with a promise as part of array or object

I am trying to queue a bunch of asynchronous calls to fire in parallel. However, the promises I am queuing have additional data that I want to keep along with the promise value.
My question is: how can I pass an object or array, which contains a promise, and have the promises resolve to a value within the object?
For example, let’s generate a normal array of promises:
async function asyncFunction(input) {
return input * 10;
}
async function main() {
var promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asyncFunction(i));
}
var result = await Promise.all(promises);
document.getElementById('output').innerText = JSON.stringify(result);
}
main();
<div id='output'></div>
This is working just fine. But now if we try to place the promise into an object, with some metadata:
async function asyncFunction(input) {
return input * 10;
}
async function main() {
var promises = [];
for (let i = 0; i < 10; i++) {
promises.push({
id: i,
value: asyncFunction(i)
});
}
var result = await Promise.all(promises);
document.getElementById('output').innerText = JSON.stringify(result);
}
main();
<div id='output'></div>
The value in value is a promise, and not the resolved value.
My expected output is:
[{"id":0,"value":0},{"id":1,"value":10},{"id":2,"value":20},...]
You can push promises that resolve to the format you want:
async function asyncFunction(input) {
return input * 10;
}
async function main() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(
asyncFunction(i).then(value => ({
id: i,
value,
}))
);
}
let result = await Promise.all(promises);
console.log(result);
}
main();
You could map over the array of promises and await the value key containing the promise to resolve
async function asyncFunction(input) {
return input * 10;
}
async function main() {
var promises = [];
for (let i = 0; i < 10; i++) {
promises.push({
id: i,
value: asyncFunction(i)
});
}
var result = await Promise.all(promises.map(async (promise) => ({
id: promise.id,
value: await promise.value
})));
document.getElementById('output').innerText = JSON.stringify(result);
}
main();

Categories

Resources