Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。
generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
回顾下上文提到的解决异步的手段:
那么,上文我们提到promsie已经是一种比较流行的解决异步方案,那么为什么还出现Generator?甚至async/await呢?
该问题我们留在后面再进行分析,下面先认识下Generator
执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
形式上,Generator函数是一个普通函数,但是有两个特征:
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
函数只能返回一次,但是,如果换成generator,就可以一次返回一个数,不断返回多次。
生成器是在函数内部运行的一些代码
这种“返回”不是传统的从函数 return。所以它被赋予了一个特殊的名称——yield。
生成器语法因语言而异。Javascript 的生成器语法类似于 PHP,但是区别也很大,如果你希望它们的作用相同,那么最终你会感到非常困惑。
在 javascript 中,如果想要使用生成器,则需要:
Generator.prototype.next()返回一个由 yield表达式生成的值。
Generator.prototype.return()返回给定的值并结束生成器。
Generator.prototype.throw()向生成器抛出一个错误。
Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
通过yield关键字可以暂停generator函数返回的遍历器对象的状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上述存在三个状态:hello、world、return
通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
done用来判断是否存在下个状态,value对应状态值
yield表达式本身没有返回值,或者说总是返回undefined
通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
正因为Generator函数返回Iterator对象,因此我们还可以通过for...of进行遍历
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for...of进行遍历了
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
回顾之前展开异步解决的方案:
这里通过文件读取案例,将几种解决异步的方案进行一个比较:
所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
fs.readFile('/etc/fstab', function (err, data) {
if (err) throw err;
console.log(data);
fs.readFile('/etc/shells', function (err, data) {
if (err) throw err;
console.log(data);
});
});
readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行
Firefox (SpiderMonkey) 在 JavaScript 1.7 中也实现了一个较早版本的生成器,其中函数声明中的星号(*)不是必需的 (只需在函数体中使用yield 关键字)。但是,旧式生成器已弃用。不要使用它们;他们将被删除 (bug 1083482)。
Generator.prototype.next() Non-Standard返回 yield 表达式产生的值。与 ES2015 生成器对象的 next() 方法对应。
Generator.prototype.close() Non-Standard关闭生成器,因此执行该函数后调用next() 函数时将会抛出 StopIteration 错误。与 ES2015 生成器对象的 return() 方法对应..
Generator.prototype.send() Non-Standard用于将值发送到生成器。 该值由 yield 表达式返回,并且返回下一个 yield 表达式产生的值。send(x) 对应于 ES2015 生成器对象中的 next(x)
Generator.prototype.throw() Non-Standard向生成器抛出错误。与 ES2015 生成器对象的 throw() 方法对应。
// 正常函数
function F () {
this.a = 1
}
const f = new F()
// 调用构造函数F,返回实例对象f
// 将构造函数内部中的this指向这个实例对象
// 将构造函数中的原型对象赋值给实例对象的原型
// 执行构造函数中的代码
// 调用Generator函数会返回遍历器对象,而不是实例对象,因此无法获取到this指向的实例对象上的私有属性和方法。但是这个遍历器对象可以继承Generator函数的prototype原型对象上的属性和方法(公有属性和方法)。
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
上面代码表明,Generator 函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
function* flatten3(arr) {
let length = arr.length;
for (let i=0; i
function flatten2(arr) {
const stack = [...arr];
const res = [];
while (stack.length) {
// 从栈里取出
const next = stack.pop();
if (Array.isArray(next)) {
// 把next扁平化,然后放入stack中
stack.push(...next);
} else {
res.push(next);
}
}
// reverse to restore input order
return res.reverse();
}
console.log(flatten2(arr))
Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
readFile('/etc/fstab').then(data =>{
console.log(data)
return readFile('/etc/shells')
}).then(data => {
console.log(data)
})
这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强
yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
将上面Generator函数改成async/await形式,更为简洁,语义化更强了
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
通过上述代码进行分析,将promise、Generator、async/await进行比较:
Generator是异步解决的一种方案,最大特点则是将异步操作同步化表达出来
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
// 卸载UI
loader.next()
包括redux-saga中间件也充分利用了Generator特性
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;
还能利用Generator函数,在对象上实现Iterator接口
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
给大家分享我收集整理的各种学习资料,前端小白交流、学习交流,也可以直接问我,我会组织大家一起做项目练习,帮助大家匹配一位学习伙伴互相监督学习-下面是学习资料参考。
留言与评论(共有 0 条评论) “” |