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