17

I'd like to take away the pain from web3 development callback hell and use Promise pattern and async/await keywords on both client side and on Node.js.

Do there exist any web3.js builds or forks that would implement promise pattern instead of callback pattern? E.g. one could say

let result = await myfilter.get()

Instead of:

 myfilter.get(function cb(error, res) {});
8
  • A similar question as declared as off topic - ethereum.stackexchange.com/questions/46197/… Jun 11, 2018 at 10:10
  • A valid comment @TrevorOakley - I think instead of closing the question it should have been migrated to StackOverflow that is a better medium for dealing with questions that clearly need more programming tutoring. Jun 12, 2018 at 9:11
  • I just have a gripe with differing standards. For developers without a team then these forums are very important. Hence there should be a wide latitude in posting questions. Jun 12, 2018 at 10:03
  • Yes. Instead of closing questions there should be at least a comment where to get further help if the people closing the question feel if there is something wrong with the question. Jun 12, 2018 at 10:37
  • My only issue is different standards for different people. That seems to be based on rep from what I have seen. Jun 12, 2018 at 14:08

5 Answers 5

9

Web3 1.0 Provides Promises in addition to callbacks:

To help web3 integrate into all kind of projects with different standards we provide multiple ways to act on asynchronous functions. Most web3.js objects allow a callback as the last parameter, as well as returning promises to chain functions.

https://web3js.readthedocs.io/en/v1.2.1/callbacks-promises-events.html

In fact, because of ethereums 'promiEvents' you can use promises to break down transactions at each step of the transaction:

web3.eth.sendTransaction({from: '0x123...', data: '0x432...'})
.once('transactionHash', function(hash){ ... })
.once('receipt', function(receipt){ ... })
.on('confirmation', function(confNumber, receipt){ ... })
.on('error', function(error){ ... })
.then(function(receipt){
    // will be fired once the receipt its mined
});
1
  • 2
    @SubhodI npm show web3 versions then npm i install web3@1.0.0
    – Tim Wu
    Jan 20, 2018 at 9:05
21

This can be accomplished with a simple wrapper:

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

      resolve(res);
    })
  );

To use it, just wrap your web3 calls as follows:

const accounts = await promisify(cb => web3.eth.getAccounts(cb));

If you want to promisify all web3 methods, you can use an ES6 Proxy:

// simple proxy to promisify the web3 api. It doesn't deal with edge cases like web3.eth.filter and contracts.
const proxiedWeb3Handler = {
  // override getter                               
  get: (target, name) => {              
    const inner = target[name];                            
    if (inner instanceof Function) {                       
      // Return a function with the callback already set.  
      return (...args) => promisify(cb => inner(...args, cb));                                                         
    } else if (typeof inner === 'object') {                
      // wrap inner web3 stuff                             
      return new Proxy(inner, proxiedWeb3Handler);         
    } else {                                               
      return inner;                                        
    }                                                      
  },                                                       
};                                                         

const proxiedWeb3 = new Proxy(web3, proxiedWeb3Handler);

And now use web3 as a promisified API:

const accounts = await proxiedWeb3.eth.getAccounts();
8
  • 3
    The best answer
    – Sandwich
    Aug 15, 2017 at 16:27
  • 1
    This comment helped me a ton! However, I did run into an error trying to use await as the author suggests. My console says that await can only be used with asyn functions... not sure why that error was happening. Instead, I created a bunch of "promises" using the wrapper, and tied it to a `Promise.all([p1,p2...]).then(function(results))... This worked to get multiple async functions to work together Nov 6, 2017 at 9:09
  • @ShawnTabrizi await statements need to be in an async function. doc
    – 0xcaff
    Nov 6, 2017 at 17:04
  • 1
    Totally get that. However following the pattern you suggest in your post leads to an error. Maybe I am missing something Nov 6, 2017 at 17:07
  • 1
    @ShawnTabrizi Please open a question on stackoverflow and link it here.
    – 0xcaff
    Nov 7, 2017 at 0:04
8

If you want to have promises with contracts, have a look at Truffle Artifactor.

For the basic asynchronous methods of Web3, I made this Gist: https://gist.github.com/xavierlepretre/90f0feafccc07b267e44a87050b95caa

I paste here the version at time of writing:

module.exports = {
    promisify: function (web3) {
        // Pipes values from a Web3 callback.
        var callbackToResolve = function (resolve, reject) {
            return function (error, value) {
                    if (error) {
                        reject(error);
                    } else {
                        resolve(value);
                    }
                };
        };

        // List synchronous functions masquerading as values.
        var syncGetters = {
            db: [],
            eth: [ "accounts", "blockNumber", "coinbase", "gasPrice", "hashrate",
                "mining", "protocolVersion", "syncing" ],
            net: [ "listening", "peerCount" ],
            personal: [ "listAccounts" ],
            shh: [],
            version: [ "ethereum", "network", "node", "whisper" ]
        };

        Object.keys(syncGetters).forEach(function(group) {
            Object.keys(web3[group]).forEach(function (method) {
                if (syncGetters[group].indexOf(method) > -1) {
                    // Skip
                } else if (typeof web3[group][method] === "function") {
                    web3[group][method + "Promise"] = function () {
                        var args = arguments;
                        return new Promise(function (resolve, reject) {
                            args[args.length] = callbackToResolve(resolve, reject);
                            args.length++;
                            web3[group][method].apply(web3[group], args);
                        });
                    };
                }
            });
        });
    },
};
2
1

web3.eth.sendTransaction function returns combined Promise + Event.

  • Promise returns reciept
  • Event allows us to subscribe also to reciept, transactionHash, confirmation, error

Additionally we can use callbacks which correspond to Events above.

For more information see:

//----------------------

if we use async/await with sendTransaction it returns reciept. But it will happened only when the transaction receipt will be ready. This may take some time.

If we would like to get any of results that provide by Events we can wrap call of sendTransaction by custom Promise.

For Example we can get transactionHash that returns immediatly in this way:

function sendTransactionTxHashHelper(from: string, to: string, value: string) {
    return new Promise((resolve, reject) => {

        this.web3.eth.sendTransaction({
            from: from,
            to: to,
            value: value,
        }).on('transactionHash', (transactionHash) => {
            resolve(transactionHash);
        });
    });
}

let transactionHash;

try {
    transactionHash = await this.sendTransactionTxHashHelper(
        '0x1234',
        '0x5678',
        '10000000'
    );
} catch (e) {
    console.log(`Error: ${e.name}:${e.message}`, e.stack);
}

console.log('transactionHash', transactionHash);
1
  • The original question is almost 2 years old and it was asked before web3 v1.0-beta was public.
    – Ismael
    Dec 5, 2018 at 16:43
0

It's actually a pretty simple patch to write. I wrote one myself a while ago, because I was tired of dealing with callbacks. It also allows for nice wrappers to do things like unlock an account, do some stuff, then lock the account again, e.g.:

var readFile = Promise.denodeify(fs.readFile);

function whileUnlocked(web3, account, pwfile, task) {
  return readFile(pwfile, 'utf8')
    .then(function(pw) {
      return web3.personal.unlockAccount(account, pw.trim())
        .then(task)
        .then(function() {
          return web3.personal.lockAccount(account);
        });
    });
}

See: https://github.com/DeviateFish/web3.js/pull/1

Note that I wrote this some time ago, and this solution could definitely be improved with newer language features, or without using the Promise polyfill library I'm using (e.g. enforcing newer versions of node), so your mileage may vary.

[E] I should add that this approach doesn't touch the filters API, since that's not strictly compatible with the Promise style. Promises are generally about things that are done asynchronously once, not (potentially) multiple times. I was unsure how to best address that discrepancy, so I left them alone.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.