Generator
介绍
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator函数有两个特征:
function关键字与函数名之间有个星号;
函数内部使用yield表达式
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } //如上代码有三个状态, 'hello'、'world'、'ending'
执行
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个迭代器对象,调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
let hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
应用
generator是实现状态机的最佳结构。
let flag = true; function clock(){ if(flag){ console.log("tick");} else { console.log("tock"); } flag = !flag; } function * clock_generator(){ while(true){ console.log("tick"); yield; console.log("tock"); yield; } } clock();//tick滴 clock();//tock答 var cg=clock_generator(); cg.next();//tick {value:undefined,done:false} cg.next();//tock {value:undefined,done:false}
案例:长轮询
// 定义一个generator函数,其返回值是一个promise let ajax = function* () { yield new Promise((resolve, reject) => { setTimeout(() => { resolve({ code: 0 }) }, 1000); }) } // 定义一个轮询函数,当轮询结果的code!=0时,1秒之后开始下一次轮询 let pull = function () { let generator = ajax(); let step = generator.next(); step.value.then((v) => { if (v.code != 0) { setTimeout(() => { console.log('wait'); pull(); }, 1000); } else { console.log(v); } }) } pull(); // 执行
案例:抽奖
// 抽奖处理 let draw = function (count) { // 此处省略具体的抽奖逻辑 console.log(`剩余${count}次`); } // 根据剩余次数判断是否执行抽奖处理 let residue = function* (count) { while (count > 0) { count--; yield draw(count); } } // 初始化抽奖次数为5次 let star = residue(5); // 创建抽奖按钮 let btn = document.createElement('button'); btn.id = 'start'; btn.textContent = '抽奖'; document.body.appendChild(btn); // 绑定抽奖事件 document.getElementById('start').addEventListener('click', () => { star.next(); }, false)
异步操作的同步化。可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
function* main() { let result = yield request("http://some.url"); let resp = JSON.parse(result); console.log(resp.value); } function request(url) { // response参数会当做上一个yield表达式的返回值 axios.get(url).then(function(response){it.next(response);}); } it = main(); it.next();
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Generator函数</title> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script> function* test(handle) { // 状态机通用函数 handle(); // 状态机通用变量 let a = 1; // 状态机不通用res let res = yield getData(); console.log(res); handle(); console.log(a); yield "结束了"; } let geo = test(function () { console.log(111); }); geo.next(); function getData() { // return 'hello' return $.get('http://47.106.244.1:8099/manager/category/findAllCategory', {}, (response) => { //发起状态机内第二段程序的执行 geo.next(response); }); } </script> </head> <body> </body> </html>