为了要收集错误,我们先实现一个新的容器对象,再尝试使用之前的Either容器。先看一下用新容器的实现(有点复杂)
let R= require('ramda');
const Success = (x) =>({
errors:[],
chain: f => f(x),
map: f => {
let res = f(x);
return res instanceof Error ? Failure(x,[res]) : Success(x);
},
getOrElse:()=>x,
toString: () => `Success(${x})`,
isSuccess:true,
value:x
})
const Failure = (x,errors = []) => ({
chain: f => Failure(x),
map: f => {
let res = f(x);
res instanceof Error && errors.push(res);
return Failure(x,errors);
},
getOrElse:(v)=>v,
toString: () => `Left(${x})`,
isSuccess:false,
value:errors
});
const mobileReg = /^1[123456789]\d{9}$/;
const vEmpty = v => v.length !== 0 ? v : new Error('手机号码为必须项!');
const vMobile = v => mobileReg.test(v.trim()) ? v : new Error('请输入正确的手机号码!')
const fromError = v => v instanceof Error ? Left(v) : Success(v);
const highFn = Fn => R.pipe(Fn,fromError);
//compose的逻辑是,只要有一个error就永远返回Left,并且不打断后续函数链的执行,同时收集错误
const compose = (...Fns)=>{
var value,errors = [];
return R.composeWith((Fn, res) =>{
let result = Fn(res);
result instanceof Error && errors.push(result)
return Fn === Fns[0] ? errors : res
})(Fns)
}
const getRes = compose(
vMobile,
vEmpty,
R.identity
)
const result = getRes('');
console.log(result);
上面的代码能实现错误收集,但是,它的compose方法过于复杂,并且还新定义了一个容器(Success和Failure)。假使compose的复杂是避免不了的,那么我们可不可以依然使用前面的Either容器,哪怕这会使得compose更加复杂。因为我们想用Either来打造一种通用的分支处理的编程范式。
初步实现:
let R= require('ramda');
const Right = x =>({
chain: f => f(x),
map: f => Right(f(x)),
getOrElse:()=>x,
toString: () => `Right(${x})`,
value:x,
isRight:true
})
const Left = x => ({
chain: f => Left(x),
map: f => Left(x),
getOrElse:(v)=>v,
toString: () => `Left(${x})`,
value:x,
isRight:false
});
const mobileReg = /^1[123456789]\d{9}$/;
const vEmpty = v => v.length !== 0 ? v : new Error('手机号码为必须项!');
const vMobile = v => mobileReg.test(v.trim()) ? v : new Error('请输入正确的手机号码!')
const compose = (...Fns)=>{
var value,errors = [];//用闭包将被执行的值和收集的错误保存起来
return R.composeWith((Fn, res) =>{
let result = res.chain(Fn);
result instanceof Error && errors.push(result)
return ( Fn !== Fns[0] ) ? res :
( errors.length ===0 ) ? Right(res.value) : Left(errors);
})(Fns)
}
const getRes = compose(
vMobile,
vEmpty,
Right
)
const result = getRes('');
console.log(result.value);
上面的代码实现了错误收集的需求,不过,其compose的实现比之前更加复杂,我们尝试简化这个compose,我们的逻辑只不过是:用给定的参数逐个调用组合中的函数(并不是链式调用),有错则将错误收集并放在Left容器中返回。因此使用ramda的composeWith并不合适,反倒是增加了复杂度,我们用ap来试一下,看一下新实现:
const compose = (...Fns)=> value => {
let res = R.ap(Fns,[value]);
let errors = R.filter((v)=>v instanceof Error,res);
return errors.length > 0 ? Left(errors) : Right(value);
}
完整代码如下:
let R= require('ramda');
const Right = x =>({
chain: f => f(x),
map: f => Right(f(x)),
getOrElse:()=>x,
toString: () => `Right(${x})`,
value:x,
isRight:true
})
const Left = x => ({
chain: f => Left(x),
map: f => Left(x),
getOrElse:(v)=>v,
toString: () => `Left(${x})`,
value:x,
isRight:false
});
const mobileReg = /^1[123456789]\d{9}$/;
const vEmpty = v => v.length !== 0 ? v : new Error('手机号码为必须项!');
const vMobile = v => mobileReg.test(v.trim()) ? v : new Error('请输入正确的手机号码!')
const compose = (...Fns)=> value => {
let res = R.ap(Fns,[value]);
let errors = R.filter((v)=>v instanceof Error,res);
return errors.length > 0 ? Left(errors) : Right(value);
}
const getRes = compose(
vMobile,
vEmpty,
Right
)
const result = getRes('');
console.log(result.value);
这个compose的实现是不是比之前的更好呢?肯定的!不止是更好,写这个compose的过程中,还让人受到了启发,开始了更深邃的思考...
我发现,错误收集并不是一个分支问题,这是从因为要调用ramda的ap方法而查看ap的文档时醒悟的,看一下ap的说明:将函数列表作用于值列表上。这里面并没有逻辑分支:所有函数都必须执行!那么这就仅仅是一个方法列表的调用问题,把值收集起来就可以了,根本不需要Either,任何容器都不需要。所以非常抱歉,前面的代码都是脱裤子放屁。看一下最后的实现:
let R= require('ramda');
const mobileReg = /^1[123456789]\d{9}$/;
const vEmpty = v => v.length !== 0 ? v : new Error('手机号码为必须项!');
const vMobile = v => mobileReg.test(v.trim()) ? v : new Error('请输入正确的手机号码!')
const getRes = (...Fns) =>
R.compose(
R.applySpec({
isValidated:R.propEq('length',0),
errors:R.identity
}),
R.filter(R.is(Error)),
R.ap(Fns),
R.of
)
const result = getRes(vMobile,vEmpty)('')
console.log(result);
(以上代码都经过测试,直接可用。)