JS-Generator 执行器的实现

本文介绍了如何实现JS Generator执行器,包括通过Thunk函数和Promise两种方式。Generator作为异步操作容器,需要执行器来控制其流程。Thunk函数通过回调函数交回执行权,Promise则利用then方法。文章详细阐述了两种方法的实现原理,并提供了相关示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

两种方法可以实现执行器:

  • 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权;
  • Promise 对象。将异步操作包装成 Promise 对象,用 then 方法中交回执行权

Thunk 函数实现 generator 执行器

在js中实现异步的方式为回调函数,比如读取文件的操作:

fs.readFile(path, function callback(err, data) {
  console.log(data);
});

上面的 fs.readFile 有两个参数,第一个路径 path,第二个为回调函数;如果将 readFile 函数改成单参数的函数,类似于函数柯里化,那么结果如下:

function thunkFile(path) {
  return function(cb) {
    return fs.readFile(path, cb)
  }
}

经过转换,变为了单参数的函数,每次只接受一个参数,调用 thunkFile 函数读取文件:

let thunk = thunkFile('./a.txt');
thunk(function(err, data) {
  if(err) return;
  console.log(data);
});

其中 thunk 就是所谓的 thunk 函数。所谓的 thunk 函数也就是接受一个回调函数为参数的函数。

任何接受回调函数为参数的函数,都可以写成 thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。

// es5版本
function Thunk(fn) {
  return function() {
    let args = [...arguments];
    return function(cb) {
      args.push(cb);
      return fn.apply(this, args);
    };
  }
}

generator 要想实现异步操作,可以在 yield 后面接上异步操作的表达式,但是问题是如何才能够保证前一步 yield 执行完了再执行下一步 yield 呢,仅仅自执行肯定是不行的,例如下面的自执行函数

function* gen() {
  let result1 = yield readFile('./a.txt');
  let result2 = yield readFile(`${result1}.txt`);
  console.log(result1);
  console.log(result2);
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}

上面代码中,Generator 函数gen会自动执行完所有步骤。但是,这不适合异步操作。因为 result1 还没有返回,第二个 yield 可能就已经执行了。必须要让 第二个yield 拿到了第一个 yield 的结果之后再执行,这就要求必须在第一个 yield 的回调函数中(因为回调函数中有结果)交回函数的执行权,即执行 g.next(),这样才能够保证第一个 yield 有了结果之后再执行第二个 yield。

按照这个思路来手动执行上面的 generator 函数:

let g = gen();
let info1 = g.next();
info1.value(function(err, data) {
  if(err) throw err;
  let info2 = g.next(data);
  info2.value(function(err, data) {
    if(err) throw err;
    g.next(data)
  });
})

仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入next方法的value属性。这使得我们可以用递归来自动完成这个过程。

到这里,可以自己写一个通用的基于 Thunk 函数的 generator 执行器。

function run(genFn) {
  let gen = genFn();
  function next(err, data) {
    let result = gen.next();
    if(result.done) return result.value;
    result.value(next);
  }
  next();
}

上面代码的run函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next 方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出。

有了这个执行器,执行 Generator 函数方便多了。不管内部有多少个异步操作,直接把 Generator 函数传入run函数即可。当然,前提是每一个异步操作,都要是 Thunk 函数,也就是说,跟在yield命令后面的必须是 Thunk 函数。

var g = function* (){
  var f1 = yield readFileThunk('fileA');
  var f2 = yield readFileThunk('fileB');
  // ...
  var fn = yield readFileThunk('fileN');
};

run(g);

Thunk 函数并不是实现 generator 函数自执行的唯一办法,因为自执行的关键是,必须有一种机制,自动控制 generator 函数的流程,接收和交换函数的执行权。回调函数可以做到这一点, Promise 也可以。

Promise 实现 generator 执行器

Promise 实现 generator 执行器的原理是:将异步操作包装成 Promise 对象,用 then 方法中交回执行权

例如文件读取是异步I/O操作,先将读取操作包装成一个promise对象,然后使用then来获取执行权。

let fs = require('fs');
function readFile(path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, (err, data) => {
      if(err) return reject(err);
      resolve(data); 
    });
  });
}

function* genReadFile() {
  let f1 = yield readFile('./a.txt');
  let f2 = yield readFile('./b.txt');
}

基于 promise 的 generator 自执行器。

function run(genFn) {
  let gen = genFn();

  function next(data) {
    let result = gen.next(data);
    result.value.then(_data => {
      next(_data);
    });
  }

  next();
}

// 调用执行器
run(genReadFile);

总结

实现异步 generator 执行器的关键是要确保上一个 yield 返回结果了之后,再继续调用生成器对象的 next() 方法执行下一个 yield。对于异步操作来说,只有在回调函数或者 promise.then 中可以保证当前的异步执行完毕有了结果,所以有了基于 Thunk 函数和promise 对象这两种方式的 generator 执行器。

故,要使用 generator 执行器的话,generator 中的 yield 后面必须接 Thunk 函数或者 promise 对象才行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值