11

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) {});
  • A similar question as declared as off topic - ethereum.stackexchange.com/questions/46197/… – Trevor Oakley Jun 11 '18 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. – Mikko Ohtamaa Jun 12 '18 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. – Trevor Oakley Jun 12 '18 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. – Mikko Ohtamaa Jun 12 '18 at 10:37
  • My only issue is different standards for different people. That seems to be based on rep from what I have seen. – Trevor Oakley Jun 12 '18 at 14:08
2

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.

http://web3js.readthedocs.io/en/1.0/callbacks-promises-events.html?highlight=promises

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
});
  • 2
    @SubhodI npm show web3 versions then npm i install web3@1.0.0 – Tim Wu Jan 20 '18 at 9:05
16

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();
  • 3
    The best answer – Sandwich Aug 15 '17 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 – Shawn Tabrizi Nov 6 '17 at 9:09
  • @ShawnTabrizi await statements need to be in an async function. doc – 0xcaff Nov 6 '17 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 – Shawn Tabrizi Nov 6 '17 at 17:07
  • 1
    @ShawnTabrizi Please open a question on stackoverflow and link it here. – 0xcaff Nov 7 '17 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);
                        });
                    };
                }
            });
        });
    },
};
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.

0

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);
  • The original question is almost 2 years old and it was asked before web3 v1.0-beta was public. – Ismael Dec 5 '18 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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