generator & co & koa

迭代器

可迭代: 一个对象上的 [Symbol.iterator]能返回一个函数,执行这个函数能返回一个迭代器。则这个对象是可迭代的;

ES6中可迭代对象,StringArrayTypedArrayMapSet的原型上就有[Symbol.iterator]方法。

迭代器: 一个对象,上面有一个next方法,调用next时每次返回{value: anything, done: true/false}。

迭代器跟其他语言中的迭代器类似,python里面也有一样的概念,调用next、C++则用++取后一个值。

举几个例子:

  • 数组的迭代器;
'use strict';

var arr = ['a', 'b', 'c'];
var arrIterator = arr[Symbol.iterator]();
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
// { value: 'a', done: false }
// { value: 'b', done: false }
// { value: 'c', done: false }
// { value: undefined, done: true }
  • 给不可迭代的对象加个迭代器,让其可迭代;
var table = {a: '1', b: '2'};

table[Symbol.iterator] = function () {
  var keys = Object.keys(this).sort();
  var index = 0;

  return {
    next: function () {
      return {
        value: keys[index], done: index++ >= keys.length
      };
    }
  }
}

for (var key of table) {
  console.log(key + ' = ' + table[key]);
}

// a = 1
// b = 2
  • 劫持[Symbol.iterator]
arr[Symbol.iterator] = () => {
  return {
    value: 0,
    next: function () {
      if (this.value < 2) {
        this.value += 1;
        return {value: this.value, done: false};
      } else {
        return {done: true};
      }
    }
  };
}

console.log([...arr]);

// [ 1, 2 ]

生成器

上面的迭代器写法可以方便得写出每次迭代执行一样操作的迭代器;

而如果每次迭代执行的是不一样的操作,要怎么写呢?

var genIterator = () => {
  return {
    value: 0,
    next: function () {
      if (this.value === 0) {
        this.value += 1;
        return {value: 'first call', done: false};
      } else if (this.value === 1) {
        this.value += 1;
        return {value: 'second call', done: false};
      } else {
        return {done: true};
      }
    }
  };
}

arr[Symbol.iterator] = genIterator;
console.log([...arr]);
// [ 'first call', 'second call' ]

生成器是由生成器函数返回的对象,它是既是一个迭代器可又是迭代的

var genFunc = function* () {
  yield 'first call';
  yield 'second call';
};

var emptyArr = [];
emptyArr[Symbol.iterator] = genFunc;
console.log([...emptyArr]);
// [ 'first call', 'second call' ]

var genA = genFunc();
console.log(genA.next());  // 是迭代器
// { value: 'first call', done: false }
console.log([...genA]);    // 可迭代的
// [ 'second call' ]
console.log(genA[Symbol.iterator]() === genA);
// true

除了以更简洁明了的代码达到迭代器的效果,生成器还具有更强大的能力,挂起上次执行的上下文,下次调用next时继续执行;

生成器的基本特性使用

  • 需要自己定制一个对象的遍历顺序时,或者需要对对象进行预处理函数操作时;
var randGenFunc = function* (arr) {
  // 增加预处理;
  while (arr.length > 0) {
    let idx = Math.floor(Math.random() * arr.length);
    let val = arr[idx];
    arr.splice(idx, 1);
    yield val;
  }
}
var sortedArr = [1, 2, 3, 4, 5, 6];
var randArr = randGenFunc(sortedArr);
console.log([...randArr]);
// [...random array]
console.log(sortedArr);
// []
// 将获取数据过程与处理数据的过程解耦;否则需要写到同一个for循环。
  • 节省内存,根据规则生成每一项,不用生成一个真实的数组,但是可以对这个虚拟数组进行遍历操作,可以写出无限递增的数组;
var billionGenFunc = function* () {
  var val = 0;
  while (val < 1000000000) {
    yield val++;
  }
}
var billionGen = billionGenFunc();
billionGen.next();
billionGen.next();
for (let val of billionGen) {
  console.log(val);
}
// 0
// ...
// 999999999
var operatorGenFunc = function* () {
  yield 1 + 2;
  yield 3, console.log('comma operator.');
}
var operatorGen = operatorGenFunc();
operatorGen.next();
operatorGen.next();
operatorGen.next();
  • yield* 交给另一个可迭代对象
var yieldStarGenFunc = function* () {
  yield [1, 2, 3];
  yield* [1, 2, 3];
  yield 'string';
  yield* 'string';
}
var yieldStarGen = yieldStarGenFunc();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();
yieldStarGen.next();

生成器的高级特性

  • 变量外部传递
var paramGen = function* () {
  yield 'first call';
  var param = yield 'second call';
  console.log(param);
}
var paramArr = paramGen();
console.log(paramArr.next());
console.log(paramArr.next());
console.log(paramArr.next('second param'))
// { value: 'first call', done: false }
// { value: 'second call', done: false }
// second param
// { value: undefined, done: true }
  • 把异步逻辑写成同步;
var readFileAsync = (fileName) => (callback) => {
  setTimeout(function () {
    return callback(null, 'this is file Data.');
  }, 200);
}

////////////////////////这里的写法是同步的///////////////////////////////
var asyncGen = function* () {
  var fileData = yield readFileAsync('fileName');
  console.log('fileData: ', fileData);
}

var asyncGenerator = asyncGen();

var ret = asyncGenerator.next();
if (typeof ret.value === 'function') {
  ret.value.call({}, function (err, data) {
    asyncGenerator.next(data);
  });
}
  • yield* 另一个生成器
var genA = function* () {
  yield 'A1';
  yield 'A2';
}
var genB = function* () {
  yield 'B1';
  yield* genA();
  yield 'B2';
  yield* genA();
}
var b = genB();
console.log([...b]);
// [ 'B1', 'A1', 'A2', 'B2', 'A1', 'A2' ]
  • 串行异步流程
var readFileAsync = (fileName) => (callback) => {
  setTimeout(function () {
    return callback(null, 'this is ' + fileName + ' data.');
  }, 200);
}
var genA = function* (next) {
  var data = yield readFileAsync('genA');
  console.log(data);
  yield next;
  console.log('genA ended.');
}
var genB = function* (next) {
  var data = yield readFileAsync('genB');
  console.log(data);
  yield next;
  console.log('genB ended.');
}
var genC = function* (next) {
  var data = yield readFileAsync('genC');
  console.log(data);
  yield next;
  console.log('genC ended.');
}

let genWorflowArr = [genA, genB, genC];
let workflow = genA(genB(genC()));

// 根据不同的obj、value情况,选择在不同的地方调用obj.next();out是为了支持多层genC的情况;
// 这里的value支持 function、generator;
function next(obj, value, out) {
  if (!obj) return;
  if (!value) value = {};
  if (typeof value.next === 'function') {
    next(value, null, function () {
      next(obj, null, out);
    });
  } else if (typeof value === 'function') {
    value.call({}, function (err, data) {
      next(obj, data, out);
    });
  } else {
    let ret = obj.next(value);
    if (ret.done) {
      return out();
    }
    next(obj, ret.value, out);
  }
}

next(workflow, null, function () {
  console.log('workflow done.');
});

co

co用generator实现异步任务串行调用,它实现了更多可接受的value,包括promise、object、array、function、generator;

思路是将所有的上述对象,转换成promise,然后在promise的onFulfilled时调用generator.next()。

/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

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

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);  // 在promise中return另一个promise,会等待这个promise执行完再接着往后执行。
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

到这里我们发现generator没有解决异步的问题,最终解决异步问题的还是callback、promise,generator在这里的作用是用给next传参的方式让异步调用用类似同步的方式写。

koa简介

koa是一个基于co封装的node http server,它非常精简,干了三件事

中间件调用

引入co,支持以中间件的形式串行调用任何同步、异步逻辑;

/**
 * Return a request handler callback
 * for node's native http server.
 *
 * @return {Function}
 * @api public
 */

app.callback = function(){
  if (this.experimental) {
    console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
  }
  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
  var self = this;

  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).then(function () {
      respond.call(ctx);
    }).catch(ctx.onerror);
  }
};

其中用到compose,完成了let workflow = genA(genB(genC()));的事情。

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    return yield *next;
  }
}

封装context

用context保存整个http请求的上下文,这么做是因为promise中取得前面处理流程的状态比较难,所以必须有一个上下文,保存所有处理结果。

// 无上下文
promise.resolve(function () {
  return promise.resolve('sync message.');
}).then(function () {
  setTimeout(() => {
    promise.resolve('async message.');
  }, 100);
}).then(function () {
  // 这里取不到 sync message.
});

// 有上下文
var context = {};
promise.resolve(function (context) {
  context.syncMessage = 'sync message.';
  return promise.resolve();
}).then(function () {
  setTimeout(() => {
    context.asyncMessage = 'async message.';
    promise.resolve();
  }, 100);
}).then(function () {
  // 从context上取到 sync message.
  let syncMessage = context.syncMessage;
});

封装request、response

封装一些http常用的处理流程和函数、跟express类似。

refs

MDN Iteration protocols

MDN function*

MDN yield*

MDN yield

MDN Generator

tj/co

koajskoa

koajs/compose

[《Python核心编程》]()

何为connect中间件

梁月-nodejs异步回调向yield同步的转变

上一篇:python 读取文件指定某行


下一篇:ssh建隧道简单介绍:*、本地服务穿透到外网、