2

Looking to get some advice on a pretty common scenario using Web3.js

Let's say you want to calculate the number of tokens for a specific ERC20 token contract that are present at an Ethereum Address.

To do this accurately, you will need to fetch both the balanceOf() and the decimals() values through Web3.js, and then adjust the balance to give a final value.

However, best practice for these Web3 requests is to make sure they are asynchronous, so we may get back the balance at a different time we get back the decimals.

What is the best way to display the adjusted value after both of these values are returned?

From my brief research, you can:

  • Use JavaScript Promises
  • Use Web3.js Batch
  • Nest the Asynchronous Calls

Are there any other options? Is there a best choice from the options above? Or is it scenario driven?

  • My continual research it telling me that current versions of web3.js do not support promises out of the gate, and that really I should be using eth.js to do this properly... is this an accurate assessment? – Shawn Tabrizi Nov 5 '17 at 22:28
3

As you mentioned, I think the choice is scenario-based. Specifically, how complex your requests are and how many asynchronous calls you need to make.

Other things to consider are (1) your familiarity with the alternatives, (2) what you're using for asynchronous calls in the rest of your code base - good to have consistency, and (3) your team's familiarity with the method.

I'll throw another one in there that you didn't include on your list:

  • ES8 async/await (although I'd stay this more stylistic: simpler, more concise way of coding promises/chains)

In your example, where there are only two variables you're retrieving, this is pretty simple so you're probably fine using callback / nesting your asynchronous calls. However, the more variables or asynchronous requests you have, this will start getting messy pretty quickly ("callback hell"), not to mention it may take longer to resolve your full chain since each sequential call waits until the previous call resolves until initiating.

So once you have more asynchronous calls, you can benefit from promises due to (1) cleaner syntax, and (2) concurrency by using Promise.all.

  • Do you think it is worth trying to make web3 work asynchronously or should I jump ship to ethjs – Shawn Tabrizi Nov 6 '17 at 4:16
  • I think it definitely makes sense to learn to use web3 asynchronously if you're going to be doing anything with web3 (or even javascript, generally). Btw you can promisify the web3 functions as follows: const Web3 = require('web3'); const Promise = require('bluebird'); Promise.promisifyAll(web3.eth, { suffix: 'Promise' }); Now you can use all web3 functions as promises(for example, web3.eth.getBalance => web3.eth.getBalancePromise) – carlolm Nov 6 '17 at 6:32
  • I am trying to do completely client-side things, not using Node.JS, so your tip there doesn't apply I don't think... – Shawn Tabrizi Nov 6 '17 at 6:43
  • 1
    You can definitely use promises/promisify for web3 on the client side. If you're using Babel for example, you can use those same commands except use import Web3 from 'web3' instead of const/require. – carlolm Nov 6 '17 at 6:55
  • Can you take a look at my own answer to this post and let me know if I am overlooking anything? Thanks again for your help, you pointed me in the right direction – Shawn Tabrizi Nov 6 '17 at 9:42
1

Thank you carlolm for the help. After a bit more research, it felt like promises was the right thing to do here, and the method that will be adopted by web3.js in the future by default. To solve my problem today, this is what I ended up doing:

I found this comment which suggested a simple wrapper that I could add to my project and wrap my Web3.js functions with:

const promisify = (inner) =>
    new Promise((resolve, reject) =>
        inner((err, res) => {
            if (err) {
                reject(err)
            } else {
                resolve(res);
            }
        })
    );

The author of the comment then mentions to use await to assign the variable, but this did not work for me. Instead I created a set of Promise functions using the wrapper, and had them all trigger a Promise.all.

So for my specific example in my post this is what I did:

tokenContract = web3.eth.contract(contractABI).at(contractAddress)

var dec = promisify(cb => tokenContract.decimals(cb))
var bal = promisify(cb => tokenContract.balanceOf(address, cb))
var tokName = promisify(cb => tokenContract.name(cb))
var tokSym = promisify(cb => tokenContract.symbol(cb))

Promise.all([dec, bal, tokName, tokSym]).then(function ([decimal, balance, tokenName, tokenSymbol]) {
    var adjustedBalance = balance / Math.pow(10, decimal)
    var output = adjustedBalance + " " + tokenSymbol + " (" + tokenName + ")";
    console.log(output)
})

Output would look like:

64.09856462716048 OMG (OMGToken)

I hope this helps someone else too!

EDIT: After making the outer function asynchronous, I was able to make the await stuff work:

async function getERC20Balance() {
    var address, contractABI, contractAddress, tokenContract, balance, decimals, tokenName, tokenSymbol, adjustedBalance
    address = document.getElementById("address").value
    contractAddress = document.getElementById("contractAddress").value
    contractABI = human_standard_token_abi

    tokenContract = web3.eth.contract(contractABI).at(contractAddress)

    decimals = promisify(cb => tokenContract.decimals(cb))
    balance = promisify(cb => tokenContract.balanceOf(address, cb))
    tokenName = promisify(cb => tokenContract.name(cb))
    tokenSymbol = promisify(cb => tokenContract.symbol(cb))

    adjustedBalance = await balance / Math.pow(10, await decimals)

    document.getElementById("tokenBalance").innerHTML = adjustedBalance;
    document.getElementById("tokenInfo").innerHTML = " " + await tokenSymbol + " (" + await tokenName + ")";
}
  • Cool, that looks good. – carlolm Nov 7 '17 at 4:41
  • this is an interesting use of await: adjustedBalance = await balance / Math.pow(10, await decimals) -- I would have only ever thought to use decimals = await promisify(...) and balance = await promisify(...) -- on another note you can put the promisify calls in a try { ... } catch (error) { console.log(error) } for some nice error handling – aturc Dec 8 '18 at 16:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.