Either单词的意思是两者中的任何一个,我们在使用Eight处理问题的时候,就相当于if else 的处理过程。

我们之前在使用MayBe函子的时候,当我们在传入null的时候,我们不会去处理外部的函数fn,仅仅返回一个值为null的函子,但是不会给出任何有效的信息,他不会告诉我们是哪里出了问题,出了什么问题。

我们可以使用Either这个函子来解决这个问题,当出现问题的时候Either会给出我们有效的提示信息。

我们一个函数中如果出现异常,会让这个函数变得不纯,那我们Either函子也可以用来处理异常,下面我们来看一下,Either函子如何实现。

我们在使用Either函子的时候,因为他是二选一,所以我们需要定义两种类型,一个是Left一个是Right,在这两个类中我们分别要去定义静态的of方法去放回当前这个对象,还有构造函数和map方法。

在Left的map方法中,这里比较特殊,直接返回了this,Right的map方法和之前保持一致。

class Left {
    static of (value) {
        return new Left(value);
    }
    constructor (value) {
        this._value = value;
    }
    map (fn) {
        return this;
    }
}

class Right {
    static of (value) {
        return new Right(value);
    }
    constructor (value) {
        this._value = value;
    }
    map (fn) {
        return Right.of(fn(this._value));
    }
}

我们观察这两个函子可以发现,他们和我们之前的函子基本上是一样的,都有of方法,都有constructor和map,其实我们在写的时候都可以继承之前的Container的,我们这里就不去继承了,方便演示。我们分别创建一个函子,打印看一下。

let r1 = Right.of(12).map( x => x + 2);
let r12 = Left.of(12).map( x => x + 2);

console.log(r1); // ...14
console.log(r2); // ...12

这里我们听过打印Left和Right创建的函子可以发现Left返回的是我们直接传入的值,没有做任何的处理。Left当中的map方法是直接返回的当前对象this,并没有调用当前传入的fn。

为什么要这么做呢,我们可以在Left中嵌入一个错误消息,下面我们演示一个可能会发生错误的函数,比如我们要去把一个JSON形式的字符串,转换成一个JSON对象。

因为调用JSON.parse的时候可能出现异常,所以我们使用try…catch。如果发生异常我们不去处理的话他不是一个纯函数,现在我们希望用函数式的方式来处理,所以我们需要些一个纯函数。

这里我们在try里需要return一个函子,我们会把我们转换后的 结果交给这个函子,将来在这个函子内部去处理,我们直接返回一个正确的值,Right.of。

我们通过Right.of创建的函子,当我们调用map方法的时候,map方法传入的这个函数会去处理我们传的这个值。这里我们传递把字符串转换成对象的值JSON.parse(str)。

如果出现错误我们也要在catch中返回一个值,因为纯函数需要有输出,这个时候我们也是要返回一个函子,Either中的Left用于处理异常。

function parseJSON (str) {
    try {
        return Right.of(JSON.parse(str));
    } cache (e) {
        return Left.of({
            error: e.message
        })
    }
}

这样我们的parseJSON就写完了,这就是Either对异常的处理。