0

(EDIT: Added some code so that people can see more explicitly what's going on)

I'm a newcomer to Ethereum who's building a project that allows users to communicate with a Smart Contract through a HTML/Javascript frontend. Users sign in with their MetaMask accounts, which inject the relevant web3 provider into the page, allowing for communication between them and the contract. They carry out various operations and finally submit a piece of data to the blockchain, the transaction being signed by their MetaMask accounts. The only problem is this: within my frontend, I need to query the contract for certain information (hasVotedBefore, for instance) before the frontend can decide how to proceed (a validation request, essentially). I used async-await to force execution to wait until these values return before loading the rest of the page (this is necessary given my specification). I was using async-await to accomplish this, and it was working fine...until I discovered that MetaMask is blind to async-await Promises, and it threw a massive fit, thinking that I was using synchronous methods with no callbacks. Attempts to add or revert to ordinary (function(result, error) style callbacks did, technically, solve this problem, but at the expense of bringing back an old one, namely the inability to properly wait for the hasVotedBefore to return before proceeding. So, I need a way to wait for this method to return, but I also need it to happen in a way that Metamask is capable of recognising.

So far, I've been pointed towards EthJS, but this is for a college project, and I don't have a lot of time left. My impression is that using EthJS would require the frontend to be refactored significantly, as well as requiring the contract itself to be changed. While I'd be willing to do this if there's no other choice, I've contemplated using a handful of nested callbacks to run this instead. I am well aware of "callback hell" and the loathed and feared status of nested callbacks among the web development community, but I just need it to work once. This code will never be used for any commercial project, and I'm running low on time, so please take those factors into account. All I need is a way of getting the frontend to wait for an asynchronous response, in a manner that MetaMask will recognise.

For instance, let's take the code for finding how many Proposals there are (this is part of the functionality of the deployed Contract) and then populating an array based on that.

async function getNumProps(){
  console.log("getNumProps");
try{
numProps = await RegionalContract.howManyProposals();
}catch (err) {
console.log(err);
}
return numProps;
}
**irrelevant code**
async function populateProps(){
try{
console.log("populateProps");
numProps = await getNumProps();
**do a bunch of stuff that's not relevant here**
}catch(error){
console.log(error);
}
}
populateProps();

When I try to run this code, I get the following in the console:

Error: The MetaMask Web3 object does not support synchronous methods like eth_call without a callback parameter. See 
https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-cli

Given what's been said already, what would you recommend that I do in order to get things working? Any advice is appreciated.

Thanks

  • 1
    Let me know if this solves your problem. If so, I will be happy to write an answer which dictates this info. – Shawn Tabrizi Apr 21 '18 at 21:18
  • Oh, thanks! I've seen your work before, and it was of help to me. It's good to meet you properly. It's too late in the night for me to work now, but I'll try that tomorrow and get back to you. – Eoin Moloney Apr 21 '18 at 21:34
1

The async/await patern works with JavaScript Promises.

In order to make web3.js functions asynchronous, you need to pass a callback parameter:

As this API is designed to work with a local RPC node, all its functions use synchronous HTTP requests by default.

If you want to make an asynchronous request, you can pass an optional callback as the last parameter to most functions. All callbacks are using an error first callback style:

web3.eth.getBlock(48, function(error, result){
    if(!error)
        console.log(JSON.stringify(result));
    else
        console.error(error);
})

If you want to then make this work with the async/await pattern, you will need to then wrap this asynchronous function into a promise, which is of the format:

new Promise( /* executor */ function(resolve, reject) { ... } );

Rather than do this over an over for each web3.js function, you can automate this process by creating a wrapper for javascript functions which includes the callback parameter and wraps it in a promise as so:

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

This can then be used really simply with nearly all existing web3.js functions:

async function getBalance() {
    var address, wei, balance
    address = document.getElementById("address").value;
    wei = promisify(cb => web3.eth.getBalance(address, cb)) // simply wrap web3.js functions with the promisify function, also passing in a callback parameter
    try {
        balance = web3.fromWei(await wei, 'ether')
        document.getElementById("output").innerHTML = balance + " ETH";
    } catch (error) {
        document.getElementById("output").innerHTML = error;
    }
}

All of this information can be found on my blog where you can also find other small projects I have done which use this pattern.

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.