resultify

1.1.2 • Public • Published

resultify

Handle errors with async/await without try/catch.

Example

const fs = require('fs')
const { resultify } = require('resultify')
 
const readFile = resultify(fs.readFile)
 
async function main () {
  const { err, data: file } = await readFile(__filename, 'utf8')
  if (err) {
    console.error('oops', err)
    return
  }
 
  console.log('echo file')
  console.log(file)
}

The resultify library is exactly the same as util.promisify except the promise will return { err, data } instead of throwing.

The promise returned from resultify will never throw / reject.

This module is great for people that like async/await and do not like try/catch.

Motivation

There are good and part parts to Promises. async & await is definitely one of the good parts.

Unfortunately promises merge Operational & Programmer errors into a single error that you have to try / catch for.

Go has a really nice pattern for handling errors explicitely

buf, err := ioutil.ReadAll(f)
if err != nil {
  return nil, errors.Wrap(err, "read failed")
}

Their approach is to use multiple return values, unfortunately multiple return values do not exist in javascript but we do have multiple arguments to functions which is why the function done(err, value) {} callback has always worked nicely.

Using destructuring

One language feature JavaScript does have is destructuring so we can do const { err, data } = await x()

async function readFile (path) {
  const { err, data: fd } = await open(path, 'r')
  if (err) {
    return { err: new Error('open failed') }
  }
 
  const buf = Buffer.alloc(64 * 1024)
  const { err: readErr } = await read(fd, buf, 0, buf.length, 0)
 
  const { err: closeErr } = await close(fd)
  if (closeErr) {
    return { err: new Error('close failed') }
  }
 
  if (readErr) {
    return { err: new Error('read failed') }
  }
 
  return { data: buf }
}

Using { err, data } as the API for all your async functions works nicely and makes error handling explicit and removes throw, try & catch.

This is great for your application code but how do you interact with the echo system of modules in core & node_modules ?

Using first & third party modules

The package resultify is very similar to util.promisify but instead of returning a promise that rejects on err it will return a Promise that always resolves to { err, data }.

Now you can do :

const fs = require('fs')
const { resultify } = require('resultify')
 
const open = resultify(fs.open)
const read = resultify(fs.read)
const close = resultify(fs.close)

If you have a third party library from NPM, like node-fetch you can use the resultifyP function to wrap it into returning a Promise that returns { err, data }

const fetchAsPromise = require('node-fetch')
const { resultifyP } = require('resultify')
 
const fetch = resultifyP(fetchAsPromise)
 
async function main() {
  const { err, data: res } = await fetch('https://nodejs.org/en/')
  assert.ifError(err)
 
  assert.ok(res)
  assert.equal(res.status, 200)
}

See also

If you want to read some of my other related projects; check out:

Yet another module ???

The source code is pretty small, do you really need a module for a bit of wrapper try / catch code ???

Honestly, we don't need this module in any of our apps, we just need a few strategic try / catch {} statements to make the rest of the application use { err, data } = await x() natively.

However, it's pretty hard to document this pattern, this README seems like a fantastic place to document the IDEA itself.

Feel free to use { err, data } = await x() without importing this library, in fact please do !

Documentation.

This package implements & exports three functions, resultify, resultifyP and resultifySync.

The resultifySync function exists mostly as a helper to wrap around JSON.parse in case you want to disallow try / catch in your codebase.

const fn = resultify(original)

Calling resultify on a function returns a promise returning function similar to how util.promisify works.

The original function is expected to be a normal nodejs cb last function, i.e. the last argument is a callback.

The returned function fn does not take a callback and instead returns a promise that resolves to { err, data } which are the two arguments for the callback.

const fn = resultifyP(original)

Calling resultifyP on a function returns a promise returning function similar to how util.callbackify works.

The original function is expected to be a promise returning function.

The returned function fn has the same arguments and returns a promise which does not reject and resolves to { err, data } which are the fulfilled and rejected value of the original promise.

const fn = resultifySync(original)

This function wraps a function that throws and returns { err, data } synchronously without returning a promise. Useful for JSON.parse

const parse = resultifySync(JSON.parse)
 
const { err, data } = parse('maybe good; maybe bad.')

No promises involved.

Installation

npm install resultify

Special Thanks

Special thanks goes to jkroso who was kind enough to give me the npm name resultify.

Check out some of his open source projects in Julia.

Contributors

  • Raynos

MIT Licenced.

Readme

Keywords

none

Package Sidebar

Install

npm i resultify

Weekly Downloads

0

Version

1.1.2

License

none

Unpacked Size

16.7 kB

Total Files

6

Last publish

Collaborators

  • jkroso
  • raynos