接触过react的小伙伴们,相信大家对于函数式编程绝对不陌生,函数式编程的纯粹,无副作用,让人着迷。在我们工作中也会大量使用Promise,其实在Promise设计时也借鉴了函数式编程中的思想:Monad。暂时不牵扯的太深,先让我们先从最基本的概念:纯函数,函子(functor)说起吧!
1. 纯函数
纯函数对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。见例子:
var arr = [1, 2, 3, 4, 5, 6]
// slice就是纯函数,没有副作用,固定输入,固定输出
arr.slice(0, 3) // [1, 2, 3]
// 相等
arr.slice(0, 3)
// splice就是不纯的,他有副作用,固定的输入,没有固定的输出
arr.splice(0, 3) // [1, 2, 3]
// 不等
arr.splice(0, 3) // [4, 5, 6]
2. 函子 functor
1)函子
函子是实现了map方法并遵守一些特殊规定的容器类型,本质是还是方法。我们先看一个简单的functor:
function functor(value) {
this._value = value
}
functor.prototype.map = function(fn) {
return new functor(fn(this._value))
}
console.log(new functor(2).map(x => x + 3)) // functor {_value: 5}
经常new functor不是很方便,而且看起来也不很函数式,所以,我们将代码改造成这样:
function functor(value) {
this._value = value
}
functor.of = function(value) {
return new functor(value)
}
functor.prototype.map = function(fn) {
return functor.of(fn(this._value))
}
console.log(functor.of(2).map(x => x + 3)) // functor {_value: 5}
functor是对函数调用的抽象,我们赋予容器去调用函数的能力,通过map一个方法,容器可以自由的决定何时去操作这个函数,以致于拥有错误处理,异步处理等能力。
2)常见函子
(1)Maybe函子
可以检查value是否为null或者undefined的函子。
function Maybe(value) {
this._value = value
}
Maybe.of = function(value) {
return new Maybe(value)
}
Maybe.prototype.isNothing = function() {
return this._value === null || this._value === undefined
}
Maybe.prototype.map = function(fn) {
return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this._value))
}
// 读取age,操作age
Maybe.of({ name: '张三', age: 12 })
.map(user => user.age)
.map(age => age * 10) // Maybe {_value: 120}
// 读取age,此时age不存在
Maybe.of({ name: '张三' })
.map(user => user.age)
.map(age => age * 10) // Maybe {_value: null}
(2)Either函子
我的的容器无法做到对错误的异常捕获处理,而promise中是可以调用catch集中处理异常的。所以我们也需要这种函子。如果操作正确,就返回正确的结果,如果操作失败,则返回错误描述。Left和Right就是Either函子的子类。
// 异常处理
function Left(value) {
this._value = value
}
Left.of = function(value) {
return new Left(value)
}
// 注意这里,什么都不处理,只展示第一次of传入的值
Left.prototype.map = function(fn) {
return this
}
// 正常流程
function Right(value) {
this._value = value
}
Right.of = function(value) {
return new Right(value)
}
Right.prototype.map = function(fn) {
return Right.of(fn(this._value))
}
function parseJSON(str) {
try {
return Right.of(JSON.parse(str))
} catch(e) {
return Right.of(e)
}
}
// 成功结果
parseJSON('{"name": "zs"}')
.map(user => user.name) // Right {_value: "zs"}
// 异常结果
parseJSON('{name: "zs"}')
.map(user => user.name) // Left {_value: 'SyntaxError'}
(3)IO函子
在我们工作中会遇到各种副作用的场景,或者依赖外部环境的,比如dom操作,接口请求,定时器等。IO函子和前面的函子不同的是,他传入的value是一个函数,他把不纯的操作包裹到一个函数中,延迟这个操作,所以IO函子入参就是包裹副作用的函数。
function IO(value) {
this._value = value
}
IO.of = function(value) {
return new IO(value)
}
IO.prototype.map = function(fn) {
return IO.of(fn(this._value()))
}
// 获取页面title
IO.of(_ => window.document)
.map(d => d.title) // IO {_value: '你的页面标签名称'}
(4)Monad函子
在实际应用中,我们可能有这样一种场景:
function Monad(value) {
this._value = value
}
Monad.of = function(value) {
return new Monad(value)
}
Monad.prototype.map = function(fn) {
return Monad.of(fn(this._value))
}
Monad.of(Monad.of(3)).map(x => x + 2) // Monad {_value: "[object Object]2"}
这个时候发现,我们无法操作容器内的值了。所以我们增加了join方法
Monad.prototype.join = function() {
return this._value ? this._value : Monad.of(null)
}
Monad.of(Monad.of(3)).join().map(x => x + 2) // Monad {_value: 5}
Monad可以将一个运算过程,通过函数拆解成互相连接的多个步骤。你只要提供下一步运算需要的函数,整个运算就可以自动进行下去。
3. Promise
Promise是解决异步编程的一种方案,替代多层嵌套的回调,Promise是个构造方法,用于封装异步操作,并获得成功或失败的结果。Promise一旦创建立即执行。
每个Promise有3种状态,pending:进行中,fullfilled:成功,rejected:失败。状态只能从pending到fullfilled,或者pending到rejected,状态不可逆。
同上面的函子的概念来讲,Promise本质上也是一个容器,提供了then方法让容器去调用,从而让函数调用自动执行。而且将包裹了副作用,不直接调用副作用,实现了纯函数的效果。
介绍Promise的文章很多,这里不再详细赘述,给大家手撸一个简易版的Promise吧。
const PENDING = 'pending'
const FULLFILLED = 'fulfilled'
const REJECT = 'reject'
function IPromise(executor) {
this.state = PENDING
this.value = null
this.reason = null
this.onFullFilledQueue = []
this.onRejectedQueue = []
const resolve = value => {
if (this.state !== PENDING) {
return
}
this.state = FULLFILLED
this.value = value
if (this.onFullFilledQueue.length === 0) {
return
}
this.onFullFilledQueue.forEach(onFullFilled => {
this.value = onFullFilled(this.value)
})
}
const reject = value => {
if (this.state !== PENDING) {
return
}
this.state = REJECT
this.reason = value
if (this.onRejectedQueue.length === 0) {
return
}
this.onRejectedQueue.forEach(onRejected => {
this.reason = onRejected(this.reason)
})
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
IPromise.prototype.then = function(onFullFilled, onRejected) {
onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : value => value
return new IPromise((resolve, reject) => {
switch(this.state) {
case PENDING:
this.onFullFilledQueue.push(() => {
try {
resolve(onFullFilled(this.value))
} catch (error) {
reject(error)
}
})
this.onRejectedQueue.push(() => {
try {
reject(onRejected(this.reason))
} catch (error) {
reject(error)
}
})
break
case FULLFILLED:
try {
resolve(onFullFilled(this.value))
} catch (error) {
reject(error)
}
break
case REJECT:
try {
reject(onRejected(this.reason))
} catch (error) {
reject(error)
}
break
}
})
}
IPromise.prototype.catch = function(onRejected) {
try {
this.then(null, onRejected)
} catch (error) {}
}
简易版的IPromise实现了状态变更,链式调用,catch捕获。
happy hack!