Task函子可以帮我们控制副作用进行异常处理,还可以处理异步任务,因为异步任务会带来回调地狱问题。
使用Task函子可以避免出现回调的嵌套,因为异步任务的实现过于复杂,所以这里我们使用folktale库中提供的Task函子来进行演示。
folktale是一个标准的函数式编程库, 他和lodash,ramda不同的是,它里面没有提供很多功能性的函数,他只提供了和函数式处理相关的操作,例如compose,curry等,他还提供了一些函子,例如Task,Eight,MayBe等。
我们先来演示一下folktale中的compose和curry如何使用。

const { curry } = require('folktale/core/lambda');
let f = curry(2, (x, y) => {
   return x + y;
});
console.log(f(1, 2));

这里面的curry和lodash有所不同,这里面接收两个参数,第一个参数用来指明函数参数有几个参数。文档上说这里传递第一个参数的目的是为了避免一些错误。
下面演示下compose,这是函数组合的意思,我们这里不自己写函数了,直接使用lodash的函数,我们把数组中的第一个元素取出来,并且转换成大写。

const { compose } = require('folktale/core/lambda');
const { toUpper, first } = require('lodash/fp');
let f = compose(toUpper, first)
console.log(f(['a', 'b']));

这里的compose函数和lodash中的flowRight用法是一样的。
接下来我们介绍下folktale中提供的Task函子处理异步任务。folktale2.x中的Task和folktale1.x中的Task使用方式区别很大,1.x中的用法更接近现在使用的函子,我们这里以2.x来演示。无非就是api的不同,我们可以通过查阅文档来了解使用。
我们通过读取一个文件,来演示下异步任务。具体在folktale的什么位置我们需要自己翻阅文档去了解。
这里提供的task是一个函数形式,这个函数会返回一个函子对象,在1.x中提供的是一个类。
接着我们写一个读取文件的函数readFile, 这个函数接收一个文件路径参数,返回一个task函子。
task这个函数本身需要接收一个函数,而这个函数的参数是固定的,叫做resolver,resolver是一个对象,它里面有两个方法,一个是resolve,执行成功之后调用的方法,还一个reject,执行失败之后执行的方法,他使用起来非常像Promise。
我们在这个函数中读取文件。

const { task } = require('folktale/concurrency/task');
const fs = require('fs');
function readFile(filename) {
   return task(resolver => {
       fs.readFile(filename, 'utf-8', (err, data) => {
           if (err) {
               resolver.reject(err);
           } else {
               resolver.resolve(data)
           }
       })
   })
}

当我们调用这个readFile函数的时候,他会返回一个Task函子,当我们想要读取文件的话,我们需要调用Task函子提供的run方法。

readFile('package.json').run();

我们可以通过listen方法监听文件读取状态,这里传入一个对象,对象中包括onRejected回调和onResolved回调。

readFile('package.json').run().listen({
   onRejected: err => {
       console.log(err);
   },
   onResolved: value => {
       console.log(value);
   }
})

此时我们再去执行代码,就会发现这个文件已经读取到了,我们如果想要处理拿到的值,我们可以在run之前调用一下Task函子的map方法,在map方法里面可以处理拿到的结果。这样更符合函数式编程。
在map方法里我们会去处理我们拿到这个文件的返回结果,所以我们在使用函子的时候,我们就没有必要去想它里面的实现机制了,之前是自己写函子,我们了解内部实现机制,而我们实际开发的过程中我们就直接使用。

readFile('package.json').map(value => {
   console.log(value); // 处理文件
   retrun value;
}).run().listen({
   onRejected: err => {
       console.log(err);
   },
   onResolved: value => {
       console.log(value);
   }
})