Loading

ES6实现generator的自动调用

简述

首先我们知道generator的内部实现采用了一种类似于协程的方法,即在在函数执行的过程之中遇到yield关键字时,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。

同步任务

对于同步任务而言,我们只需要保证执行完毕即可,不需要确保执行的顺序问题

直接使用for...of...循环 或者使用递归实现

异步任务

JavaScript 语言对异步编程的实现,就是回调函数。

如果我们能在回调函数之中重新激活generator中的被冻结的调用栈,不断重复,直至全部调用完成,这样就实现了generator的自动调用

自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权

对于回调函数的处理主要下面介绍Thunk 函数和promise两种方法

Thunk 函数

在 JavaScript 语言中通过Thunk函数,把一个具有回调函数包装成一个只接受回调函数作为参数的单参数函数。

例如

fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName);
readFileThunk(callback);

我们可以把任何的函数都写成Thunk 函数

function thunkify(fn) {
    return function() {
      var args = new Array(arguments.length);
      var ctx = this;
  
      for (var i = 0; i < args.length; ++i) {
        args[i] = arguments[i];
      }
  
      return function (done) {
        var called;
  
        args.push(function () {
          if (called) return;
          called = true;
          done.apply(null, arguments);
        }); // 确保回调函数只会执行一次
  
        try {
          fn.apply(ctx, args);
        } catch (err) {
          done(err);
        }
      }
    }
  };

根据上述的包装后的函数,generator的自动调用实现如下

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

  function next(err, data) {
    var result = gen.next(data);// 得到当次的执行结果
    if (result.done) return;
    result.value(next);// 在回调函数中继续调用直到完成
  }

  next();
}

function* g() {
  // ...
}

run(g);

Promise实现如下

基于promise的自动执行其实就是在then的回调函数中,不断的调用执行,直到结束

function run(gen){
  var g = gen();

  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

run(gen);

基于promise有co模块这一执行器

var co = require('co');
co(gen);

co模块就是对于上述的promise的进一步封装

  • co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。
  • 在返回的 Promise 对象里面,co 先检查参数gen是否为 Generator 函数。如果是,就执行该函数,得到一个内部指针对象;如果不是就返回,并将 Promise 对象的状态改为resolved。
  • 接着,co 将 Generator 函数的内部指针对象的next方法,包装成onFulfilled函数。这主要是为了能够捕捉抛出的错误
  • next函数,它会反复调用自身。
function co(gen) {
  var ctx = this;

  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.call(ctx);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
  });
}


function next(ret) {
  if (ret.done) return resolve(ret.value); // 检查当前是否为 Generator 函数的最后一步,如果是就返回。
  var value = toPromise.call(ctx, ret.value); // 确保每一步的返回值,是 Promise 对象。
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  return onRejected( // 在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为rejected,从而终止执行。
    new TypeError(
      'You may only yield a function, promise, generator, array, or object, '
      + 'but the following object was passed: "'
      + String(ret.value)
      + '"'
    )
  );
}
posted @ 2021-01-24 16:28  不吃苦瓜^  阅读(196)  评论(0编辑  收藏  举报