Generator函数
function* name() {
yield
}
返回一个value和done结果
value是yield的值
done是boolean
yield本身没有返回值,总是返回undefined
next也可以带参数
具有iterator接口可以被for of循环
介绍
- Generator函数是什么
- Generator函数的特征特性
- Generator函数的应用场景
Generator函数是什么
Generator 函数是 ES6 提供的一种异步编程解决方案
Generator函数的特征和特性
- function关键字与函数名之间有一个星号
- 函数体内部使用yield语句-定义不同状态
- Generator函数具有Iterator属性-可遍历
- Generator函数是分段执行,具有记忆功能
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
Generator函数的应用场景 (代码讲解见最后)
- 异步操作的同步化表达
- 控制流管理
- 部署iterator接口
yield(暂停、记忆)
(1)遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined
function* fn(){
let n = yield 'hello'
return n
}
let f = fn()
f.next() // { value: 'hello', done: false }
f.next() // { value: undefined, done: true }
next
function* fn(){
let i = 0
while(true) {
let a = yield i
i += 1
// console.log(a) // a 是什么? 打印看一下
if(a) { i = 0 }
}
}
let f = fn()
f.next() // { value: 0, done: false }
f.next() // { value: 1, done: false }
f.next() // { value: 2, done: false }
f.next() // { value: 3, done: false }
f.next(true) // { value: 0, done: false }
f.next() // { value: 1, done: false }
f.next() // { value: 2, done: false }
yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
Generator作为对象属性
let json = {
*fn() {
yield 1
yield 2
return 'c'
}
}
let f = json.fn()
f.next()
f.next()
f.next()
for-of
function* fn(){
yield 1
yield 2
yield 3
yield 4
yield 5
yield 6
yield 7
yield 8
yield 9
return 'done'
}
for(let val of fn()) {
console.log(val)
}
for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。
Generator函数的应用场景
异步操作的同步化表达 (模拟UI展示)
function* loadUI() {
showLoadingScreen()
yield loadUIDataAsynchronously()
hideLoadingScreen()
}
var loader = loadUI()
loader.next() // 加载UI
loader.next() // 卸载UI
上图中的Generator函数loadUI第一次执行赋值给变量后只是返回并赋值了一个遍历器,只有执行遍历器next方法时才会执行showLoadingScreen显示界面并且执行loadUIDataAsynchronously异步加载数据,等到数据加载完毕再使用next方法执行hideLoadingScreen隐藏界面,好处是逻辑非常清楚
异步操作的同步化表达 (模拟AJAX异步获取数据)
function* main() {
var result = yield request("/hello");
console.log('main到了' + result)
var resp = JSON.stringify(result);
console.log('JSON解析了' + resp)
}
function request(url) {
$.get(url, function(response){
setTimeout(function(){alert('get到了' + response)}, 2000)
it.next(response);
})
}
var it = main();
$('button').on('click', function(){it.next()})
// 'main到了' + result
// 'JSON解析了' + resp
// 弹出'get到了' + response
上面代码的写法完全和同步一样,就是多了yield语句,在$.get函数的next方法中必须加入response参数,因为yield表达式本身返回的是undefined
控制流管理
如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
采用Promise改写上面的代码。
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
用promise改写回调只是换了不同的写法,在代码运行流程方面generator函数可以进一步改善
Generator改善回调函数(控制流管理)
function* longRunningTask(value1) {
try {
console.log(value1)
var value2 = yield value1 + 1;
console.log(value2)
var value3 = yield value2 + 2;
console.log(value3)
var value4 = yield value3.a == a; // 报错被catch捕捉
console.log(value4)
var value5 = yield value4 + 4;
console.log(value5)
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
console.log(e)
}
}
function scheduler(task) { // 这里传的task参数是*longRunningTaks()返回的遍历器,只有这个遍历器执行next方法后才有状态对象返回
var taskObj = task.next(task.value); // 每次返回generator函数的执行状态对象 例:{value: 1, done: false}
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
scheduler(longRunningTask(0))
// scheduler(longRunningTask(initialValue));
Generator函数同步项目操作的情况
let jobs = [ // jobs内封装了多个项目操作
{ steps: [ () => console.log(1), () => console.log(2), () => console.log(3) ] },
{ steps: [ () => console.log(4), () => console.log(5), () => console.log(6) ] },
{ steps: [ () => console.log(7), () => console.log(8), () => console.log(9) ] }
];
function *iterateSteps(steps){
for (let i=0; i< steps.length; i++){
let step = steps[i];
yield step();
}
}
function *iterateJobs(jobs){
for (let i=0; i< jobs.length; i++){
let job = jobs[i];
yield *iterateSteps(job.steps);
} // 这个函数一次给jobs上的多个任务加上了yield *
}
let g = iterateJobs(jobs) // 获得项目jobs封装的遍历器
for(let step of g){console.log(step)} // 最后使用for of循环一次执行所有任务所有步骤
这个方法只适用于同步操作的情况
部署iterator接口
function* iterEntries(obj) {
let keys = Object.keys(obj); // keys为obj的key的数组
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