Thunk 是什么?

我们在很多文章或者第三方库中均见到过 Thunk 这个词出现。但其含义着实难以理解,即使对源码翻来覆去看也不知其与上下文有什么联系。针对这个概念我在网上浏览了一些文章及查阅了维基文档,以下便是我的一些理解,希望能对你也能有所帮助。

在编程语言刚起步的时候,有不同的求值策略,即函数的参数该在什么时候求值。其中一种叫 传值调用(call by value), 顾名思义就是在函数被调用前其参数的值就已经被编译器给算好了,每次调用函数都会用同样的参数值。另一种策略叫 传名调用(call by name),也就是只有当函数真正被调用的时候才去计算参数的值(惰性求值),在此过程中编译器其实已经把惰性求值的过程包装成了一个名叫 Thunk 辅助函数,函数被调用时,先调这个辅助函数求出参数值,再进入函数主体。这也许是最早 Thunk 这个概念被使用的时候了。

大致我们可以定义 Thunk 的具体含义如下:

Thunk 是一类函数的别名,主要特征是对另外一个函数包装一下,添加了一些额外的操作。其主要用途为延迟函数执行(惰性求值)或者给一个函数执行前后添加一些额外的操作。

当然,对于我们开发者来说我们基本不用关心函数参数是怎么求值的,一般我们使用的编程语言都已经决定好了。比如JavaScript,就是用的 传值调用 策略。

以上就是我关于 Thunk 这个概念的理解,并附上一些代码中具体的例子:

普通代码:

// total变量会被立即求值
const total = 1 + 2;
​
// 只有当我们调用sum函数的时候,1+2的计算才会真正执行
const sum = () => 1 + 2;

redux-thunk:

// ThunkActionCreator
function fetchUser(id) {
  // 返回的这个函数既是一个 Thunk, 或者叫 ThunkAction
  return async ({ dispatch }) => {
    // 额外的异步API调用
    const user = await api.getUser(id);
    // 此时才真正dispatch action
    dispatch({ type: 'UPDATE_USER', payload: user });
  }
}

Node.js:

const util = require('util');
const fs = require('fs');
​
// stat函数是一个 Thunk
// - 只有执行stat的时候才会执行真正额fs.stat函数
// - stat还把fs.stat包装成了具有promise接口的函数
const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
});