Result

A Result is a data type for modeling errors. Unlike try/catch and throw, it can give you more control on propagating those errors in a pure way. When in doubt, if it's async and it can fail, use Promise. If it's sync and it can fail, use Result.

// [jwarden 3.10.2019] TODO: break down this massive code example and provide an easier one at first.

const Result = require('folktale/result')
const { Ok, Error } = Result
const fs = require('fs')

// async
const readFile = file =>
    new Promise((success, failure) =>
        fs.readFile(file, (err, data) =>
            err
            ? failure(err)
            : success(data)
        )
    )

readFile('non-existent-file.cow')
.then(contents => console.log("contents:", contents))
.catch(error => console.log("file read error:", error))
// file read error: { Error: ENOENT: no such file or directory, open 'non-existent-file.cow'

// sync
const readFileSync = file => {
    try {
        const data = fs.readFileSync(file)
        return Ok(data)
    } catch(readError) {
        return Error(readError.message)
    }
}

const result = readFileSync('non-existent-file.cow')
// result: folktale:Result.Error({ value: "ENOENT: no such file or directory, open 'non-existent-file.cow'" })

Ok and Error

The two sub-types of Result are Ok and Error. Ok means whatever you were doing worked, and Ok will contain the data. It could also just mean "things didn't blow up".

The Error contain what went wrong. This can be any data type you want. If it's one thing, a String is usually best. If you don't know, the original native Error is better. If you decide to create a custom one, be careful of name collisions. Destructuring Error leads to confusion when you start going new Error('t3h failurez') and get back a Folktale Result folktale:Result.Error({ value: "t3h failurez" }) instead of what you thought, a native error with stack trace included Error: t3h failurez.

When many things can go wrong, its better to use a Union Type (see next section, Part 6: Union Types).

Chi Chi Chain Chain

Like Promise and Maybe, you can chain Result. The chain method works like the others; good things like Ok will continue the chain. As soon as an Result.Error is returned, the whole chain is aborted.

Below, everything is fine:

const Result = require('folktale/result')
const { Ok, Error } = Result
const { startCase } = require('lodash/fp')

const safeSplit = name => {
    try {
        const result = name.split(' ')
        return Ok(result)
    } catch(error) {
        return Error(error.message)
    }
}

console.log(
    Ok('warden jesse')
    .chain(name => safeSplit(name))
    .chain(name => Ok(name.reverse()))
    .chain(name => Ok(startCase(name)))
)
// folktale:Result.Ok({ value: "Jesse Warden" })

However, if we inject bad data at the beginning:

console.log(
    Ok(undefined)
    .chain(name => safeSplit(name))
    .chain(name => Ok(name.reverse()))
    .chain(name => Ok(startCase(name)))
)
// folktale:Result.Error({ value: "Cannot read property 'split' of undefined" })

Just like Promise and Maybe; good values will keep chaining, bad ones immediately abort.

Getting Data Out

Like Maybe, if there is data in the Ok, you can get it out, else supply a default in case of an Error:

const castSpellResult = Error('Failed, not enough mana.')
const didSpellWork = castSpellResult.getOrElse('Spell casting failed.')

console.log(didSpellWork)
// Spell casting failed

const attackResult = Ok('Success, 4 points of damage!')
const didAttackWork = attackResult.getOrElse('Attack failed.')
console.log(didAttackWork)
// Success, 4 points of damage!

Pattern Matching

Like Maybe and Validator, you can use matchWith to pattern match on the Result:

const attackResult = Ok( { hit: true, attacker: 'Jesse', target: 'Bad Guy', weapon: 'Boomerang' } )
const printedResult = attackResult.matchWith({
    Ok: ( { value } ) =>
        value.hit
        ? `${value.attacker} successfully hit ${value.target} with ${value.weapon}!`
        : `${value.attacker} missed ${value.target} with ${value.weapon}...`
})

console.log(printedResult)
// Jesse successfully hit Bad Guy with Boomerang!

try

Just like using promisify became a habit to more easily wrap callback methods to return a Promise instead, so too is it common to wrap methods that can throw errors with Result using Result.try:

const result = Result.try(() => JSON.parse(undefined))

console.log(result)
// folktale:Result.Error({ value: SyntaxError: Unexpected token u in JSON at position 0 })

Conclusions

Use Result when something can go wrong, typically a native Error is thrown, or a bunch of things can go wrong. When it's ok, like an Array not having your data, or an environment variable not being set, use a Maybe. If you need to know a series of Errors, Validation is probably better. Learn more from the Folktale Result documentation.

results matching ""

    No results matching ""