再谈副作用

2,206 阅读5分钟

理解副作用是深入理解现代前端框架的前提。

副作用是什么

副作用(side-effect)是指让一个函数变得不再纯净(pure)的东西。一个纯净的函数,无论何时何地(any time any wherer)执行,都会得到稳定的结果,这对保障程序的稳定性和性能都有极大的帮助。那么反过来,如果一个函数不能any time any wherer得到稳定的结果,那这个函数就不是纯净的,就是有副作用了。

常见副作用包括:对外部可变数据或变量的修改,外部接口的调用尤其是IO,异常的抛出。因为它们都会让函数的执行不再稳定,要么会导致函数输出变化,要么导致函数报错。 举一些例子:

  • 对外部可变数据或变量的修改: 全局变量 / 闭包变量 / dom对象 / bom对象的读写操作
  • 外部接口的调用尤其是IO:dom对象 / bom对象的方法调用; xhr / fetch这样的网络IO;console / LocalStorage这样的磁盘IO
  • 异常的抛出:函数中的某些代码可能会抛出异常或者执行出错

通俗的来讲就是:不能相信除了自己以外的任何人。

实际上,在FP中function指的是纯净的函数,对于有副作用的函数称之为procedure.

副作用是有害的

副作用让我们的程序变得不稳定,举个例子:

window.a = 0
function doWork() {
	return window.a + 1
}

这个doWork就是一个副作用函数的代表,它依赖了外部的可变数据变得不再稳定。因为谁也不知道什么时候会在另外的地方对window.a做了修改。

在我们的业务代码中有很多这样的“稳定”的函数,我们总是“愿意相信”它们不会出问题。然而这样的函数事实上就是不稳定的,因为它依赖了外部的可变数据。其实一些lib比如webpack要比我们理性的多,它们会不带任何主观因素的把这样的函数视为带有副作用的函数。

我们经常有一个误解是:我们总认为我们以为的稳定的函数是纯净的,实际上并非如此:

  • 纯 -> 稳定 ✅
  • 不纯 -> 不稳定 ✅
  • 稳定 -> 纯 ❌
  • 不稳定 -> 不纯 ✅

稳定不一定是纯的,但是不纯一定是不稳定的。因此判定我们的前端程序是否真的稳定,它的依据不是表面上是否稳定,而是内在是否纯净。根据FP的定义,纯净的函数只适合用来做计算,但是计算密集型工作正好不是前端程序擅长的工作。相反前端程序几乎都是不纯净的,因为像IO、DOM、BOM等我们极度依赖的平台接口,全部都是具有副作用的。

所以,前端程序或者说任何具有实际应用价值的程序几乎都是不稳定的,我们一定要认识到这一点,只有这样我们才能理解RxJS/Redux-saga/Redux-thunk这些东西存在的意义。

最实在的例子:在我理解副作用之前,我一直觉得“先网络IO再去dispatch“和”直接dispatch一个thunk“是没有区别的。🤣

副作用是必须的

只有完全孤立的系统才不会有副作用,但是试想一下如果系统是完全孤立的,我们又如何去使用它呢?如果一个系统跟外界没有交互,那它的存在又有什么意义呢?因此副作用对任何有实际应用意义的系统而言都是必须的。

副作用就像燃气一样,如果我们对其放任不顾它就会变成散布于我们空间中的毒气,但是一旦给予它合适的管制,它就能成为推动我们空间前进的燃料。

实际上这是两件事

前面分别讲述了:

  • 副作用是有害的,我们要限制副作用
  • 副作用是必须的,我们要处理副作用

实际上这是两件不相关的事,前者关注的是副作用产生之前,而后者关注的是副作用产生之后。

也因此分别对应了不同的表现形态:

  • 对于限制副作用,它催生了FP层面的Monad/AlgebraicEffect和库层面的Rx/Elm/Flux/Redux/Saga这些东西,它们主要是用来限制副作用产生的
  • 对于处理副作用,则体现在了不同前端框架对已经产生的副作用的处理策略上

所以,当在再看到一些关于FP或者关于副作用的言论出现的时候,我们就要分清楚了:

  • 如果是基础库作者发表的,那么他们一般是在说如何限制副作用
  • 如果是前端视图库作者发表的,那么他们一般是在说如何处理副作用

如果你按照这个思路还是看不懂作者在说什么,那只能是这个作者跨界了。举个例子,React核心开发者Dan曾经谈论过Algebraic Effect,但是我看了以后就很懵逼,明明是第一种情况的一个概念,为什么跟视图库React扯到一起了。直到有一天我突然想明白了,他所谓的React中的Algebraic Effect并不是指React使用了这种方式处理副作用,而是指”React的渲染方式很像Algebraic Effect”,因为它们都有“暂停->重新执行“这样的流程。

这从原文中的这句话得以看出:

How Is All of This Relevant to React?
Not that much. You can even say it’s a stretch.

所以,虽然Dan是好意写了一篇文章给我们讲FP的一些概念,但是却导致很多人根本没法把这两者结合到一起,反而更不懂React了。