Mixing async/await, promises, and callbacks

I’ve been editing some older code, but at the same time I’ve been trying to be strict about upgrading my node.js to a more modern idiom. That means wherever and whenever possible, I want to use node tap for my tests, and async/await and Promise instead of callbacks.

In the past, I have used the async library, and then later the d3 queue library for handling asynchronous functions. But no longer—my intent is to never use those libraries again.

Of course one big barrier to all this is that I have little experience with using Promises and async/await. This is the first in hopefully a set of posts that talk about some small things that I discover. Hopefully they will be useful to someone else.

First up, mixing callbacks and promises. As I said above, I’m switching to using the node tap library for testing. The docs indicate that the callback function passed into t.test (the signature is t.test([name], [options], [function])) should return a Promise in order to inform tap that this test is asynchronous. I’m writing new tests for older code, and I don’t have the luxury of rewriting my old code to switch from callbacks to Promises. On the other hand, I didn’t want to mess around with figuring out how to use old-style node.js callback conventions with t.test.

As an example, an old library code looked like:

// pass in year, config_file (optional) in opts object
function get_wim_imputed_status(opts,cb){
    var year = +opts.year
    var o = set_couchdb_options(opts)
    o['view']= '_design/wim/_view/imputed_status'
    o['startkey'] = [year]
    o['endkey'] = [year,"\ufff0"] // verified high sentinel as of 1.6.2 couchdb
    o['reduce'] = false
    viewer(o
          ,function(err,docs){
              if(err){
                  console.log(err)
                  throw new Error('oops')
              }
               cb(null,docs)
               return null
           })
    return null
}

Pretty basic function—all it does is poke CouchDB and get some response. My old tests, using mocha, looked something like this:

    it('should get the imputed status of all the wim sites 2012',function(done){
        wim_sites.get_wim_imputed_status({'year':2012
                                          ,'config_file':config_file}
                                         ,function(e,r){
                                             should.not.exist(e)
                                             should.exist(r)
                                             r.should.have.property('rows').with.lengthOf(165)
                                             ... other stuff ...
                                             return done()
                                         })
    })

Eventually I will rewrite everything to use Promises, doing that now would also require rewriting my viewer library, and then rewriting all of the code that uses the viewer library, and so on. Someday maybe, but not today. So what I really need is a way to quickly wrap this callback in a Promise.

There are a few libraries that exist to wrap callback style functions in Promises. Node version 8.0.0 adds a very handy function called util.promisify (source code link is here), but I’m only running node version 8 on my laptop and on Travis CI, so that won’t work for me.

While the node.js Promisify wrapper is robust and covers lots of edge cases, it is pretty easy to hack up a much simpler version for more limited use. What I did was to create the following function:

function promise_wrapper(fn,arg){
    return new Promise((resolve, reject)=>{
        fn(arg,function(e,r){
            if(e){
                console.log(e)
                return reject(e)
            }else{
                return resolve(r)
            }
        })
    })
}

A super simple wrapper. I’m pretty sure there are lots of examples just like it on the web. I use it in my tap tests as follows:

...
const tests = async (_config ) => {

    await tap.test('should get the imputed status of all the wim sites 2008',(t)=>{
        return promise_wrapper(
            wim_sites.get_wim_imputed_status
            ,{'year':2008
              ,'couchdb':_config.couchdb}
        ).then( (r) =>{
            t.ok(r)
            t.ok(r.rows)
            console.log(r.rows)
            t.is(r.rows.length,2,'got all imputed status 2008')
             ... more tests ...
            t.end()
            return null
        }).catch( err => {
            console.log(err)
            throw new Error(err)
        })
    })
    ...
    tap.end()
    return null
}

So in words, I return a Promise from the callback function passed to t.test() by returning the output of my call to my promise_wrapper function. Once I decided on that approach, it took about 10 minutes to work through all my old mocha-based tests and convert them to this new tap + promise_wrapper approach.

The commits laundry documenting my move from mocha to tap is all hanging out to dry for all to see in my github repo.

Advertisements

One thought on “Mixing async/await, promises, and callbacks

  1. Pingback: Writing tests and learning promises in node.js | Contour Line

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s