一、开始
generator函数是异步操作的一种解决方案,本文简单介绍了一下它,并介绍了gensync库的一些用法。
二、generator
1. 基础
generator函数是一个状态机,内部封装了多个状态。调用generator函数后,并不立即执行,而是返回一个指向内部状态的指针对象,也就是遍历器对象。
调用遍历器对象的next方法,会使指针指向下一个状态,也就是每次调用next,内部指针就从函数头部或上一次停下来的地方开始执行,执行遇到下一个yield表达式或者return为止。也就是遇到yield会暂停执行,调用next会恢复执行。
调用next方法返回的对象含有value和done两个属性,value是yield表达式的值或者return的值,done表示是否遍历结束。
下面是一个简单的例子:
function* testGenerator() {
yield 'hello';
yield 'world';
yield 'go';
return 'out';
}
const res = testGenerator()
res.next() // { value: 'hello', done: false }
res.next() // { value: 'world', done: false }
res.next() // { value: 'go', done: false }
res.next() // { value: 'out', done: true }
res.next() // { value: undefined, done: true }
res.next() // { value: undefined, done: true }
// ...next方法可以带1个参数,会被当作上一个yield表达式的返回值。
下面这个函数是无限运行的,可以通过传入next的参数让其重置。
function* f() {
for (let i = 0;true; i++) {
const reset = yield i;
if (reset) i = -1
}
}
const r = f()
r.next(); // { value: 0, done: false }
r.next(); // { value: 1, done: false }
r.next(true); // { value: 0, done: false } 可以理解为把 const reset = yield i 语句替换为 const reset = true
r.next(); // { value: 1, done: false }第一次使用next方法时,函数是从顶部运行到第一个yield,next中参数无效,有效的是调用函数时传入的参数。
举个例子:
function* f(arg) {
return arg
}
const a = f(1);
a.next(2); // { value: 1, done: true}
const b = f();
b.next(3) // { value: undefined, done: true}generator函数返回的遍历器对象还有return和throw方法。
2. return 和 throw
return方法会返回给定的值,并终结generator函数。
function* f() {
yield 1;
yield 2;
yield 3;
}
const a = f();
a.next(); // { value: 1, done: false }
a.return(5) // { value: 5, done: true } 可以理解为,把 yield 2 语句替换为 return 5
a.next() // { value: undefined, done: true }throw方法可以在函数体外抛出错误,在函数体内被捕获。
function* f() {
try {
yield 1
yield 2
yield 3
} catch (e) {
console.log('内部捕获', e)
}
}
const a = f()
a.next()
try {
a.throw(new Error('aa')) // 可以理解为把 yield 1 替换为 throw new Error('aa')
a.throw(new Error('bb'))
} catch (e) {
console.log('外部捕获', e)
}
// 内部捕获 Error: aa
// 外部捕获 Error: bb上面例子中的第一个错误被generator函数内部的catch捕获,由于catch函数已经执行一次了,不会捕获第2个错误,所以它会被外层的catch捕获。
3. yield*
如果在一个generator函数A内部,调用另一个generator函数B的话,需要在A内部手动遍历B,也可以使用yield *。
// 第1个generator函数
function* foo() {
yield 'a';
yield 'b';
}
// 第2个generator函数
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let i of bar()) {
console.log(i)
}
// 'x'
// 'a'
// 'b'
// 'y'三、gensync
gensync库封装generator函数,返回一个新的函数,其有不同的使用方式:
- 同步使用(
sync) - 异步调用,封装成promise,在then方法中获取结果(
async) - 传入回调使用(
errback)
举个例子:
const gensync = require('gensync');
const readFile = function* () {
return 10;
};
const readFileAndMore = gensync(function* () {
const val = yield* readFile();
return 10 + val;
});
// 同步获取结果
const code = readFileAndMore.sync('./file.js', 'utf8');
// 当作promise,在then方法中获取结果
readFileAndMore.async('./file.js', 'utf8').then((code) => {
console.log('func code2', code);
});
// 回调函数中获取结果
readFileAndMore.errback('./file.js', 'utf8', (err, code) => {
console.log('func code3', code);
});也可以传入一个包含sync/async/errback属性的对象,手动指定同步和异步操作,其中sync属性是必需的。
const readFile = gensync({
sync: fs.readFileSync,
errback: fs.readFile,
});
const code = readFile.sync('./test-for-read.js', 'utf8');
readFile.async('./test-for-read.js', 'utf8').then((code) => {
console.log('obj code2', code);
});
readFile.errback('./test-for-read.js', 'utf8', (err, code) => {
console.log('obj code3', code);
});四、总结
本文简单介绍了generator函数和gensync库的一些用法,建议阅读完本文后继续深入查看后面的相关资料,进一步加深对generator的理解。