How to understand the usage scenario of Generator in ES6

1, Introduction The Generator function is an asynchronous programming solution provided by ES6, and its syntax behavior ...
1, Introduction
2, Use
3, Asynchronous solution
4, Usage scenario

1, Introduction

The Generator function is an asynchronous programming solution provided by ES6, and its syntax behavior is completely different from that of traditional functions

Recall the above-mentioned means to solve asynchrony:

  • Callback function

  • promise

So, we mentioned above that promsie is already a popular asynchronous solution, so why does Generator still appear? Even async/await?

This problem will be analyzed later. Let's know the Generator first

Generator function

implement   Generator   The function returns an iterator object, which can be iterated in turn   Generator   Every state inside the function

Formally, the Generator function is an ordinary function, but it has two characteristics:

  • There is an asterisk between the function keyword and the function name

  • The yield expression is used inside the function body to define different internal states

function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; }

2, Use

Generator   The function returns an iterator object with the Symbol.iterator attribute and returns it to itself

function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // true

The yield keyword allows you to pause the state of the iterator object returned by the generator function

function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();

There are three statuses mentioned above: hello, world and return

The next internal state can only be traversed through the next method, and its operation logic is as follows:

  • When a yield expression is encountered, the following operations are suspended, and the value of the expression immediately following yield is taken as the value attribute value of the returned object.

  • The next time the next method is called, continue execution until the next yield expression is encountered

  • If no new yield expression is encountered, it runs until the end of the function until the return statement, and takes the value of the expression after the return statement as the value attribute value of the returned object.

  • If the function does not have a return statement, the value attribute value of the returned object is undefined

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 is used to judge whether the next status exists, and value corresponds to the status value

The yield expression itself does not return a value, or it always returns undefined

By calling the next method, you can take a parameter, which will be treated as the return value of the previous yield expression

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 a.next() // Object a.next() // Object var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }

Because the Generator function returns the Iterator object, we can also traverse through 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

The native object has no traversal interface. By adding this interface to it through the Generator function, you can use for...of to traverse

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(`$: $`); } // first: Jane // last: Doe

3, Asynchronous solution

Review previous solutions for deploying asynchronous solutions:

  • Callback function

  • Promise object

  • generator function

  • async/await

Here, we compare several asynchronous solutions through the file reading case:

Callback function

The so-called callback function is to write the second paragraph of the task in a function separately and call this function when the task is re executed

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); }); });

The third parameter of the readFile function is the callback function. The callback function will not be executed until the operating system returns the file / etc/passwd

Promise

Promise is generated to solve the callback hell. It changes the nesting of callback functions into chain calls

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) })

This form of chain operation makes the two-stage execution of asynchronous tasks clearer, but there are also obvious problems. The code becomes redundant and the semantics is not strong

generator

The yield expression can pause the function execution, and the next method is used to resume the function execution, which makes the Generator function very suitable for synchronizing asynchronous tasks

const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

async/await

Change the above Generator function to async/await, which is more concise and semantic

const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

difference:

Analyze the above codes and compare promise, Generator and async/await:

  • promise and async/await are designed to handle asynchronous operations

  • The Generator is not designed for asynchrony. It has other functions (object iteration, controlling output, deploying interleaver interfaces...)

  • promise is more complex and less readable than Generator and async

  • Generator and async need to be matched with promise objects to handle asynchronous situations

  • async is essentially the syntax sugar of Generator, which is equivalent to automatically executing the Generator function

  • async is more concise in use. Writing asynchronous code in the form of synchronization is the final solution to deal with asynchronous programming

4, Usage scenario

Generator is an asynchronous solution, and its biggest feature is to express the synchronization of asynchronous operations

function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); } var loader = loadUI(); //  Load UI loader.next() //  Uninstall UI loader.next()

The Redux saga middleware also makes full use of the Generator feature

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(); } catch (e) { yield put(); } } function* mySaga() { yield takeEvery("USER_FETCH_REQUESTED", fetchUser); } function* mySaga() { yield takeLatest("USER_FETCH_REQUESTED", fetchUser); } export default mySaga;

You can also use the Generator function to implement the Iterator interface on the object

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

1 November 2021, 03:59 | Views: 8714

Add new comment

For adding a comment, please log in
or create account

0 comments