这篇文章看一下用生成器来处理字符串流,流式数据有个好处就是不必等到所有数据都接收到,就可以进行处理。从数据处理的方向上看,可以有pull和push两种模式,传统的http就是pull的模式,而最新的WebSocket就是push的模式。生成器既可以用pull的方式也可选择push的方式进行数据处理,看你是利用yield发数据还是接数据。
下面这个例子就是处理字符串中的数字,然后简单的相加,最后得出结果。
PULL模式:
const END_OF_SEQUENCE = Symbol();
function getNextItem(iterator) {
let {value,done} = iterator.next();
return done ? END_OF_SEQUENCE : value;
}
function isWordChar(ch) {
return typeof ch === 'string' && /^[A-Za-z0-9]$/.test(ch);
}
function* tokenize(chars) {
let iterator = chars[Symbol.iterator]();
let ch;
do {
ch = getNextItem(iterator);
if (isWordChar(ch)) {
let word = '';
do {
word += ch;
ch = getNextItem(iterator);
} while (isWordChar(ch));
console.log("word=>",word);
yield word;
}
} while (ch !== END_OF_SEQUENCE);
}
function* extractNumbers(words) {
for (let word of words) {
if (/^[0-9]+$/.test(word)) {
yield Number(word);
}
}
}
function* addNumbers(numbers) {
let result = 0;
for (let n of numbers) {
result += n;
yield result;
}
}
function* logAndYield(iterable, prefix='') {
for (let item of iterable) {
console.log(prefix + item);
yield item;
}
}
const CHARS = '2 apples and 5 oranges.';
const CHAIN2 = logAndYield(addNumbers(extractNumbers(tokenize(logAndYield(CHARS)))), '-> ');
[...CHAIN2];//[2,7]
简单说一下上面这些函数的作用:
logAndYield:打印代码运行中的数据,并且把数据yield出去,充当一个数据迭代器。通过console.log出来的字符串可以看出,CHARS是一个字符一个字符被发yield出去的,并不是把整个字符串一起传递出去,这就形成了一个字符流。
tokenize:接受字符流,然后组成一个一个的单词,在yield出去,通过这个函数里面的console.log可以看出,yield出去的数据分别是:2,apples,and,5,oranges,形成了一个单词流。
extractNumber:判断接收的单词是否是数字,是的话,继续往外yield出去。
addNumbers:把接收到的所有数字相加,并逐一返回相加结果。
上面这些生成器主要是通过一些迭代器的操作方法去拉数据,也就是PULL的模式。下面在看看如果利用PUSH的模式去推数据:
function coroutine(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}
function send(iterable, sink) {
for (let x of iterable) {
sink.next(x);
}
sink.return && sink.return(); // signal end of stream
}
const logItems = coroutine(function* () {
try {
while (true) {
let item = yield; // receive item via `next()`
console.log(item);
}
} finally {
console.log('DONE');
}
});
send('abc', logItems());
上面的send函数主要利用next方法,把数据当作参数push出去,logItems中的yield起到接数据的作用。这段代码有个问题就是因为Chrome没有实现return方法,所以finally中的DONE是不能打印出来的。当然你可以把sink.return换成sink.throw,但是这样不太符合语义。
利用上面这个工具方法,就可以把处理字符串的例子改写成PUSH模式,有兴趣的自己试试吧,代码有点多,就不贴了。
*以上全部代码在Chrome 48下通过测试