Thunk 函数

1. 什么是 Thunk 函数?

JS 是传值调用,它的 Thunk 函数含义有所不同:在 JS 中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成只接受回调函数作为参数的单参数版本。

// 正常版本的 readFile(多参数)
fs.readFile(fileName, callback);

// Thunk 版本的 readFile(单参数)
const readFileThunk = Thunk(fileName);
readFileThunk(callback);

const Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

任何参数中有回调函数的函数,都能写成 Thunk 函数形式:

const Thunk = function (fn) {
  return function () {
    const args = Array.prototype.slice.call(arguments);
    return function (callback) {
      args.push(callback);
      return fn.apply(this, args);
    };
  };
};

const readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

2. 另一种实现:完全柯里化的 Thunk

如果希望 Thunk 工厂接受原始函数,调用时再依次填入参数与回调,写法更灵活:

const Thunk = function (fn) {
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    };
  };
};

// 用法一:先传参数,再传回调
const fun = Thunk(args1, args2);
fun(callback);

// fn(...args, callback); 自动执行 Generator 函数, 先传入参数, 再传入回调函数

这种写法的特点是 Thunk 工厂先接 fn,然后链式调用:Thunk(fn)(...args)(callback),每一层都"收集"参数直到最后一层再统一执行。

3. Thunk 函数有什么用?

以前用处不大,但 ES6 有了 Generator 函数后,Thunk 函数可以用于 Generator 函数的自动流程管理。

真正的威力在于可以自动执行 Generator 函数:

function run(fn) {
  const gen = fn();

  function next(err, data) {
    const result = gen.next(data);
    if (result.done) return;
    // result.value 是 Thunk 函数
    result.value(next);
  }

  next();
}

run(gen);

run 就是 Generator 的自动执行器。内部 next 先移到下一步(gen.next),判断是否结束(result.done),没结束就把 next 注入 Thunk 函数(result.value),结束则退出。

有了这个执行器,执行 Generator 函数方便很多。不管多少个异步操作,直接传入 run 即可(前提是每个异步操作都是 Thunk 函数)。

const gen = function* () {
  const f1 = yield readFile('fileA');
  const f2 = yield readFile('fileB');
  // ...
};

run(gen);

4. 其他自动执行方案

  • 回调函数:将异步操作包装成 Thunk 函数。
  • Promise 对象:将异步操作包装成 Promise,用 then 交回执行权。

co 函数库

co 可以让你不用编写 Generator 的执行器:

const co = require('co');
co(gen).then(function () {
  console.log('Generator 函数执行完成');
});

co 的原理

Generator 函数是异步操作的容器,自动执行需要一种机制:当异步操作有结果时,自动交回执行权。

两种方法可以做到:

  1. 回调函数:将异步操作包装成 Thunk 函数,在回调里交回执行权。
  2. Promise 对象:将异步操作包装成 Promise,用 then 交回执行权。

co 库其实就是把两种自动执行器(Thunk 函数 + Promise)包装成一个库。使用 co 的前提条件是:Generator 的 yield 命令后面只能是 Thunk 函数 或 Promise 对象。

来源整理自:我的有道云笔记