Search code examples
javascriptes6-promiseweb3js

Executing a code after a promise with nested promises inside it is resolved


I have the following js code structure;

Promise_1.then(function(){
  for(){
    Promise2.then(function(){
      ...
    })
  }
}).then(
  Promise_3.then(function(){
    for(){
      Promise4.then(function(){
        ...
      })
    }
  })
).then(
  function(){
    // SOME CODE
  }
)

I want to execute SOME CODE after the above promises are resolved. But the SOME CODE is executing before the above promises are resolved. I know I can enclose SOME CODE in setTimeout() which will solve the problem as suggested by other answers on SO but I think it's not a good idea. The actual code on which I am working is as follow;

user_xp = 0
connections_blocks.methods.get_deposit_blocks(current_email).call({
    from: new_web3.eth.Contract.defaultAccount
}, function (err, result) {
    deposit_blocks = result
    console.log(deposit_blocks)
    deposit_blocks = deposit_blocks.split(",")
    deposit_blocks.splice(0, 1)
    for (i_ in deposit_blocks) {
        console.log(deposit_blocks[i_])
        user_xp + new_web3.eth.getBlock(deposit_blocks[i_]).then(function (deposit_block) {
            user_xp = user_xp + deposit_block.gasUsed
            console.log(user_xp)
        })
    }
}).then(
    connections_blocks.methods.get_send_blocks(current_email).call({
        from: new_web3.eth.Contract.defaultAccount
    }, function (err, result) {
        send_blocks = result
        console.log(send_blocks)
        send_blocks = send_blocks.split(",")
        send_blocks.splice(0, 1)
        for (i_ = 0; i_ < send_blocks.length; i_ = i_ + 2) {
            console.log(send_blocks[i_])
            user_xp + new_web3.eth.getBlock(send_blocks[i_]).then(function (send_block) {
                user_xp = user_xp + send_block.gasUsed
                console.log(user_xp)
            })
        }
    })).then(
    setTimeout(function () {
        console.log(user_xp)
        xp = document.getElementById('xp')
        xp.innerHTML = "XP:" + user_xp
    },1000)
)

In the above code, I am just using setTimeout() which is solving my problem. But I want the code to execute automatically only when the above promises are resolved. Is there any easy way to do that in JS w/o putting promises in functions and making it more complex.

UPDATE

I am using the following web3's function to fetch data from the solidity's smart contract which actually returns a promise with the data as promiseValue

myContract.methods.myMethod([parameters).call(options,[callback])


Solution

  • Problems with your code include

    1. the arguments to .then must be functions
    2. if you need to wait for the Promises in the loop, you need to add them to an array and then use promise.all to wait until they're all resolved

    Try the following changes to your actual code - I'm assuming that without the node style callback, the function returns a promise that resolves to the result that would be passed to the node-style callback (which is removed in this code)

    (if you wait a while I'll flatten the promise chain - just noticed it isn't as flat as it could be

    user_xp = 0
    return connections_blocks.methods.get_deposit_blocks(current_email).call({
        from: new_web3.eth.Contract.defaultAccount
    })
    .then(function (result) {
        var promises = []
        deposit_blocks = result
        console.log(deposit_blocks)
        deposit_blocks = deposit_blocks.split(",")
        deposit_blocks.splice(0, 1)
        for (i_ in deposit_blocks) {
            console.log(deposit_blocks[i_])
            promises.push(
                new_web3.eth.getBlock(deposit_blocks[i_]).then(function (deposit_block) {
                    user_xp = user_xp + deposit_block.gasUsed
                    console.log(user_xp)
                })
            )
        }
        return Promise.all(promises)
    })
    .then(function () {
        return connections_blocks.methods.get_send_blocks(current_email).call({
            from: new_web3.eth.Contract.defaultAccount
        })
    })
    .then(function (result) {
        var promises=[]
        send_blocks = result
        console.log(send_blocks)
        send_blocks = send_blocks.split(",")
        send_blocks.splice(0, 1)
        for (i_ = 0; i_ < send_blocks.length; i_ = i_ + 2) {
            console.log(send_blocks[i_])
            promises.push(
                new_web3.eth.getBlock(send_blocks[i_]).then(function (send_block) {
                    user_xp = user_xp + send_block.gasUsed
                    console.log(user_xp)
                })
            )
        }
        return Promise.all(promises)
    })
    .then(function () {
        console.log(user_xp)
        xp = document.getElementById('xp')
        xp.innerHTML = "XP:" + user_xp
    })
    

    As an added "bonus", I believe the code can be simplified to

    return connections_blocks.methods.get_deposit_blocks(current_email).call({
        from: new_web3.eth.Contract.defaultAccount
    })
    .then(result => Promise.all(result.split(",").slice(1).map(deposit_block => new_web3.eth.getBlock(deposit_block)
        .then(deposit_block => deposit_block.gasUsed)
    })
    .then(gasUsed1Array => connections_blocks.methods.get_send_blocks(current_email).call({
            from: new_web3.eth.Contract.defaultAccount
        })
        .then(result => Promise.all(result.split(",").slice(1).filter((v, i) => !(i%2)).map(send_blocks => new_web3.eth.getBlock(send_block)
            .then(send_block => send_block.gasUsed)
        )))
        .then(gasUsed2Array => [...gasUsed1Array, ...gasUsed2Array])
    )
    .then(results => {
        user_xp = results.reduce((a, b) => a + b);
        console.log(user_xp)
        xp = document.getElementById('xp')
        xp.innerHTML = "XP:" + user_xp
    })