问答题1420/1593怎么理解ES6中 Generator的?使用场景有哪些?

难度:
2021-07-03 创建

参考答案:

一、介绍

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

回顾下上文提到的解决异步的手段:

  • 回调函数
  • promise

那么,上文我们提到promsie已经是一种比较流行的解决异步方案,那么为什么还出现Generator?甚至async/await呢?

该问题我们留在后面再进行分析,下面先认识下Generator

Generator函数

执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

形式上,Generator 函数是一个普通函数,但是有两个特征:

  • function关键字与函数名之间有一个星号
  • 函数体内部使用yield表达式,定义不同的内部状态
1function* helloWorldGenerator() { 2 yield 'hello'; 3 yield 'world'; 4 return 'ending'; 5}

二、使用

Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己

1function* gen(){ 2 // some code 3} 4 5var g = gen(); 6 7g[Symbol.iterator]() === g 8// true

通过yield关键字可以暂停generator函数返回的遍历器对象的状态

1function* helloWorldGenerator() { 2 yield 'hello'; 3 yield 'world'; 4 return 'ending'; 5} 6var hw = helloWorldGenerator();

上述存在三个状态:helloworldreturn

通过next方法才会遍历到下一个内部状态,其运行逻辑如下:

  • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  • 如果该函数没有return语句,则返回的对象的value属性值为undefined
1hw.next() 2// { value: 'hello', done: false } 3 4hw.next() 5// { value: 'world', done: false } 6 7hw.next() 8// { value: 'ending', done: true } 9 10hw.next() 11// { value: undefined, done: true }

done用来判断是否存在下个状态,value对应状态值

yield表达式本身没有返回值,或者说总是返回undefined

通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

1function* foo(x) { 2 var y = 2 * (yield (x + 1)); 3 var z = yield (y / 3); 4 return (x + y + z); 5} 6 7var a = foo(5); 8a.next() // Object{value:6, done:false} 9a.next() // Object{value:NaN, done:false} 10a.next() // Object{value:NaN, done:true} 11 12var b = foo(5); 13b.next() // { value:6, done:false } 14b.next(12) // { value:8, done:false } 15b.next(13) // { value:42, done:true }

正因为Generator 函数返回Iterator对象,因此我们还可以通过for...of进行遍历

1function* foo() { 2 yield 1; 3 yield 2; 4 yield 3; 5 yield 4; 6 yield 5; 7 return 6; 8} 9 10for (let v of foo()) { 11 console.log(v); 12} 13// 1 2 3 4 5

原生对象没有遍历接口,通过Generator 函数为它加上这个接口,就能使用for...of进行遍历了

1function* objectEntries(obj) { 2 let propKeys = Reflect.ownKeys(obj); 3 4 for (let propKey of propKeys) { 5 yield [propKey, obj[propKey]]; 6 } 7} 8 9let jane = { first: 'Jane', last: 'Doe' }; 10 11for (let [key, value] of objectEntries(jane)) { 12 console.log(`${key}: ${value}`); 13} 14// first: Jane 15// last: Doe

三、异步解决方案

回顾之前展开异步解决的方案:

  • 回调函数
  • Promise 对象
  • generator 函数
  • async/await

这里通过文件读取案例,将几种解决异步的方案进行一个比较:

回调函数

所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数

1fs.readFile('/etc/fstab', function (err, data) { 2 if (err) throw err; 3 console.log(data); 4 fs.readFile('/etc/shells', function (err, data) { 5 if (err) throw err; 6 console.log(data); 7 }); 8});

readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行

Promise

Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用

1const fs = require('fs'); 2 3const readFile = function (fileName) { 4 return new Promise(function (resolve, reject) { 5 fs.readFile(fileName, function(error, data) { 6 if (error) return reject(error); 7 resolve(data); 8 }); 9 }); 10}; 11 12 13readFile('/etc/fstab').then(data =>{ 14 console.log(data) 15 return readFile('/etc/shells') 16}).then(data => { 17 console.log(data) 18})

这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强

generator

yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化

1const gen = function* () { 2 const f1 = yield readFile('/etc/fstab'); 3 const f2 = yield readFile('/etc/shells'); 4 console.log(f1.toString()); 5 console.log(f2.toString()); 6};

async/await

将上面Generator函数改成async/await形式,更为简洁,语义化更强了

1const asyncReadFile = async function () { 2 const f1 = await readFile('/etc/fstab'); 3 const f2 = await readFile('/etc/shells'); 4 console.log(f1.toString()); 5 console.log(f2.toString()); 6};

区别:

通过上述代码进行分析,将promiseGeneratorasync/await进行比较:

  • promiseasync/await是专门用于处理异步操作的

  • Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口...)

  • promise编写代码相比Generatorasync更为复杂化,且可读性也稍差

  • Generatorasync需要与promise对象搭配处理异步情况

  • async实质是Generator的语法糖,相当于会自动执行Generator函数

  • async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案

四、使用场景

Generator是异步解决的一种方案,最大特点则是将异步操作同步化表达出来

1function* loadUI() { 2 showLoadingScreen(); 3 yield loadUIDataAsynchronously(); 4 hideLoadingScreen(); 5} 6var loader = loadUI(); 7// 加载UI 8loader.next() 9 10// 卸载UI 11loader.next()

包括redux-saga 中间件也充分利用了Generator特性

1import { call, put, takeEvery, takeLatest } from 'redux-saga/effects' 2import Api from '...' 3 4function* fetchUser(action) { 5 try { 6 const user = yield call(Api.fetchUser, action.payload.userId); 7 yield put({type: "USER_FETCH_SUCCEEDED", user: user}); 8 } catch (e) { 9 yield put({type: "USER_FETCH_FAILED", message: e.message}); 10 } 11} 12 13function* mySaga() { 14 yield takeEvery("USER_FETCH_REQUESTED", fetchUser); 15} 16 17function* mySaga() { 18 yield takeLatest("USER_FETCH_REQUESTED", fetchUser); 19} 20 21export default mySaga;

还能利用Generator函数,在对象上实现Iterator接口

1function* iterEntries(obj) { 2 let keys = Object.keys(obj); 3 for (let i=0; i < keys.length; i++) { 4 let key = keys[i]; 5 yield [key, obj[key]]; 6 } 7} 8 9let myObj = { foo: 3, bar: 7 }; 10 11for (let [key, value] of iterEntries(myObj)) { 12 console.log(key, value); 13} 14 15// foo 3 16// bar 7

最近更新时间:2024-08-14

赞赏支持

预览

题库维护不易,您的支持就是我们最大的动力!