Search code examples
javascriptnode.jsasync-awaitweb3js

How do I increase a variables value only after all the awaits is executed in a map so that I can execute the promises paralley?


Hey this is a very basic question but I am getting problem in it. I have following code:

var nonce = await web3.eth.getTransactionCount(fromAccount);
    hashes.map(async hash => {
    console.log(nonce)
    const account = await web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1)
    const transaction = await contract.methods.submitHash(hash);
    const options  = {
        nonce: web3.utils.toHex(nonce),
        to      : transaction._parent._address,
        data    : transaction.encodeABI(),
        gas     : await transaction.estimateGas({from: account.address}),
        gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')),
    };
    const signed  = await web3.eth.accounts.signTransaction(options, account.privateKey);
    const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);
    console.log(receipt)
    nonce ++}

I want to increase nonce only once all the awaits are executed each time ie, I want to get 1,2,3,4.. as nonce value. But the problem is due to asynchronus nature nonce gets the same value in each loop ie 1,1,1,1.. How to increase it by one in each loop execution?


Solution

  • The problem is that you're using map where you want a for loop. There are two problems with that:

    1. It doesn't make sense to use map when you're not using the array it returns (someone, somewhere, is teaching this anti-pattern and doing a disservice to their students); and

    2. map doesn't do anything with the promise your async function returns, so all of your async functions run in parallel

    With a for loop, you don't have those problems:

    let nonce = await web3.eth.getTransactionCount(fromAccount);
    for (const hash of hashes) {
        console.log(nonce);
        const account = await web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1);
        const transaction = await contract.methods.submitHash(hash);
        const options = {
            nonce   : web3.utils.toHex(nonce),
            to      : transaction._parent._address,
            data    : transaction.encodeABI(),
            gas     : await transaction.estimateGas({from: account.address}),
            gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')),
        };
        const signed  = await web3.eth.accounts.signTransaction(options, account.privateKey);
        const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);
        console.log(receipt);
        ++nonce;
    }
    

    Since your for is inside a context where await works (an async function or, soon, the top level of a module), it waits at the await expressions before continuing its logic.


    If the work can be done in parallel, then you could use map and await Promise.all on the array it returns. Within the map callback, use nonce + index instead of incrementing since index will be 0 for the first hash, 1 for the next, etc.:

    // In parallel
    let nonce = await web3.eth.getTransactionCount(fromAccount);
    const results = await Promise.all(hashes.map(async (hash, index) => {
        const thisNonce = nonce + index;
        console.log(thisNonce);
        const account = await web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1);
        const transaction = await contract.methods.submitHash(hash);
        const options = {
            nonce   : web3.utils.toHex(thisNonce),
            to      : transaction._parent._address,
            data    : transaction.encodeABI(),
            gas     : await transaction.estimateGas({from: account.address}),
            gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')),
        };
        const signed  = await web3.eth.accounts.signTransaction(options, account.privateKey);
        const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);
        console.log(receipt);
    }));
    // If you're going to keep using `nonce`, do `nonce += results.length` here.