0

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;
}
Homer
  • 239
  • 2
  • 11
  • how many currencies and adresses do you have? because if you have 10 currencies and 10 adresses, would it not fire 100 request parallel? not a problem? – grodzi Jan 24 '22 at 20:13

3 Answers3

2

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

Ron B.
  • 1,502
  • 2
  • 7
1

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;
}```
Zulfe
  • 820
  • 7
  • 25
0

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;
}
Raghav Garg
  • 3,601
  • 2
  • 23
  • 32