6

I have the following code that waits for a transaction to get mined on the Ethereum blockchain.

function waitForMinedTransaction(txHash, tries = 1) {
  return new Promise(function(resolve, reject) {
    web3.eth.getTransactionReceipt(txHash, function(err, res) {
      if (err) reject(err)
      if (res) resolve(res)

      // nothing yet (retry in 10 sec..)
      console.log(`Attempt #${ tries }...`)
      if (tries > 60) reject("max_tries_exceeded")
      setTimeout(function() { return waitForMinedTransaction(txHash, tries + 1) }, 10000)
    })
  })
}

The issue is that when the transaction is mined (e.g. after 10 tries), it never gets resolved. I'm sure this has something to do with setTimeout and the promise chain (where a Promise is returned instead of resolve/rejecting the current promise) but need some pointers on fixing it.

1 Answer 1

10

I would suggest to embed the chaining logic inside the promise constructor callback.

Also make sure that when you resolve or reject, you exit the function to avoid the rest of the code being executed. So put a return before the calls to resolve and reject, not that a return value has any sense, but just to make sure the rest of the function's code is not executed:

function waitForMinedTransaction(txHash) {
    return new Promise(function(resolve, reject) {
        (function attempt(triesLeft) {
            web3.eth.getTransactionReceipt(txHash, function(err, res) {
                if (err) return reject(err);
                if (res) return resolve(res);
                if (!triesLeft) return reject("max_tries_exceeded");
                console.log(`No result. Attempts left: #${ triesLeft }...`);
                setTimeout(attempt.bind(null, triesLeft-1), 10000);
            });
        })(60); // number of retries if first time fails
    });
}

If you prefer to have new promises in the chain (like you attempted to do), then the trick is to resolve with the chained promise, i.e. with the return value of the the chained call:

function waitForMinedTransaction(txHash, triesLeft = 60) {
    return new Promise(function(resolve, reject) {
        getTransactionReceipt(txHash, function(err, res) {
            if (err) return reject(err);
            if (res) return resolve(res);
            console.log(`No result. Attempts left: #${ triesLeft }...`);
            if (!triesLeft) return reject("max_tries_exceeded");
            setTimeout(_ => {
                resolve(waitForMinedTransaction(txHash, triesLeft-1));
            }, 10000);
        });
    });
}
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks! I like how you were able to avoid bubbling up and resolving all those promises by encapsulating the recursive bit. I'll be using this solution but if you were to do it, how would you actually resolve all these promises in the original nested, recursive fashion?
I have added that as an alternative to my answer just now.
Wonderful, thank you. I assume resolve(reject) would reduce to reject as one would expect?
Yes, if the promise passed as argument to resolve rejects, then the final promise will also reject. This is a required behaviour by the Promises/A+ specification, point 2.3§1: "...it attempts to make promise adopt the state of x..."
Glad to hear it was useful to you, @Vitorlui

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.