Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using setTimeout with recursive promises

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.

like image 792
FloatingRock Avatar asked Jan 02 '23 22:01

FloatingRock


1 Answers

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);
        });
    });
}
like image 102
trincot Avatar answered Jan 05 '23 16:01

trincot