迭代器
可迭代: 一个对象上的 [Symbol.iterator]能返回一个函数,执行这个函数能返回一个迭代器。则这个对象是可迭代的;
ES6中可迭代对象,String
,Array
,TypedArray
,Map
和Set
的原型上就有[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
- yield是关键字,它也有优先级
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
[《Python核心编程》]()