Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand Generators in JavaScript

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

How to understand Generators in JavaScript? in view of this problem, this article introduces the corresponding analysis and solution in detail, hoping to help more partners who want to solve this problem to find a more simple and feasible way.

Introduction to JavaScript Generators Guid

JavaScript introduced generators in ES6. Generator functions are similar to regular functions, except that they can be paused and resumed. Generators are also closely related to iterators because the generator object is an iterator.

In JavaScript, you usually cannot pause or stop a function call. (yes, asynchronous functions are paused while waiting for await statements, but asynchronous functions are only introduced when ES7. In addition, asynchronous functions are built on top of generators.) An ordinary function ends only when an error is returned or thrown.

Function foo () {console.log ('Starting'); const x = 42; console.log (x); console.log (' Stop me if you can'); console.log ('But you cannot');}

Instead, the generator allows us to pause execution at any breakpoint and resume execution from the same breakpoint.

Generators and iterators

From MDN:

In JavaScript, an iterator is an object that defines a sequence and may return a return value when terminated. More specifically, an iterator implements any object of Iterator protocol > by using the next () method, which returns an object with two properties: value, which is the next value in the sequence, and done, which is true if it has iterated to the last value in the sequence. If value and done exist together, it is the return value of the iterator.

Therefore, the essence of an iterator is:

Objects that define a sequence

There is a next () method...

Returns an object with two properties: value and done

Do you need a generator to create an iterator? No, no. In fact, we can already use the closure pre-ES6 to create an infinite Fibonacci sequence, as shown in the following example:

Var fibonacci = {next: (function () {var pre = 0, cur = 1; return function () {tmp = pre; pre = cur; cur + = tmp; return cur;};}) ()}; fibonacci.next (); / / 1fibonacci.next (); / / 2fibonacci.next (); / / 3fibonacci.next (); / / 5fibonacci.next (); / / 8

With regard to the benefits of the generator, I will refer to MDN again:

Although custom iterators are a useful tool, creating them requires careful programming because of the need to explicitly maintain their internal state. Generator functions provide a powerful alternative: they allow us to define iterative algorithms by writing a function whose execution is not continuous.

In other words, it's easier to create an iterator using a generator (no closures are required!) Which means that errors are less likely

The relationship between generators and iterators is that the generator object returned by the generator function is an iterator. Grammar

The generator function is created using the function * syntax and paused using the yield keyword.

The initial call to the generator function does not execute any of its code; instead, it returns a generator object. This value is used by calling the generator's next () method, which executes the code until it encounters the yield keyword and then pauses until next () is called again.

Function * makeGen () {yield 'Hello'; yield' World';} const g = makeGen (); / / g is a generatorg.next (); / {value: 'Hello', done: false} g.next (); / / {value:' World', done: false} g.next (); / {value: undefined, done: true}

Calling g.next () repeatedly after the last statement above will only return (or, more accurately, produce) the same return object: {value: undefined, done: true}.

Yield suspends execution

You may notice something special about the code snippet above. The second next () call produces an object whose property is done: false instead of done: true.

Since we are executing the last statement in the generator function, shouldn't the done property be true? That's not true. When a yield statement is encountered, the value that follows it (in this case, "World") is generated and execution is paused. Therefore, the second next () call pauses on the second yield statement, so execution is not complete yet-- execution is complete (that is, done: true) only when execution restarts after the second yield statement, and the code is no longer running.

We can think of the next () call as telling the program to run to the next yield statement (assuming it exists), generate a value, and pause. The program does not know that there is nothing after the yield statement until it resumes execution, and can only resume execution through another next () call.

Yield and return

In the above example, we use yield to pass values to the outside of the generator. We can also use return (as in a normal function); however, we can use return to terminate execution and set done: true.

Function * makeGen () {yield 'Hello'; return' Bye'; yield 'World';} const g = makeGen (); / / g is a generatorg.next (); / {value:' Hello', done: false} g.next (); / / {value: 'Bye', done: true} g.next (); / / {value: undefined, done: true}

Because execution is not paused on the return statement, and by definition no code can be executed after the return statement, done is set to true.

Parameters of the yield:next method

So far, we have been using yield to pass values outside the generator (and pause its execution).

However, yield is actually bi-directional and allows us to pass values to the generator function.

Function * makeGen () {const foo = yield 'Hello world'; console.log (foo);} const g = makeGen (); g.next (1); / / {value:' Hello world', done: false} g.next (2); / / logs 2, yields {value: undefined, done: true}

Wait a minute. It should not be "1" printed to the console, but the console prints "2"? At first, I found that this part was conceptually counterintuitive because my expected assignment foo = 1. After all, we pass "1" into the next () method call to generate Hello world, right?

But this is not the case. Passed to the first next (...) The value of the call is discarded. There is actually no other reason except that this seems to be the ES6 specification. Semantically, the first next method is used to start the traversal object, so there is no need to take parameters.

I like to rationalize the execution of the program like this:

On the first next () call, it runs until it encounters yield 'Hello world', to generate {value:' Hello world', done: false} and pause on this basis. That's what happened. As you can see, any value passed to the first next () call will not be used (and therefore discarded).

When next (...) is called again. Execution resumes. In this case, the execution needs to assign some value to the constant foo (determined by the yield statement). Therefore, our second call to next (2) assigns foo=2. The program doesn't stop here-it runs until it encounters the next yield or return statement. In this case, there is no more yield, so it records 2 and returns the done: true of undefined. Using async in generators because yield is a two-way channel that allows information to flow in both directions, it allows us to use generators in a very cool way. So far, we have mainly used yield to pass values outside the generator. But we can also take advantage of the bi-directional nature of yield to write asynchronous functions synchronously.

Using the above concept, we can create a basic function that is similar to synchronous code but actually executes asynchronous functions:

Function request (url) {fetch (url) .then (res = > {it.next (res); / / Resume iterator execution});} function * main () {const rawResponse = yield request ('https://some-url.com'); const returnValue = synchronouslyProcess (rawResponse); console.log (returnValue);} const it = main (); it.next (); / / Remember, the first next () call doesn't accept input

That's how it works. First, we declare a request function and a main generator function. Next, create an iterator it by calling main (). Then, we start by calling it.next ().

In the first line, function * main (), perform a pause after yield request ('https://some-url.com')). Request () implicitly returns undefined, so we actually generate an undefined value, but it doesn't matter-we don't use it.

When the fetch () call in the request () function is complete, it.next (res) will be called and do the following two things:

It continues to execute; and

It passes res to the generator function, which is assigned to rawResponse

Finally, the rest of main () is done synchronously.

This is a very basic setting and should bear some resemblance to promise. For a more detailed description of yield and asynchrony, see this article.

The generator is disposable

We cannot reuse the generator, but we can create a new generator from the generator function.

Function * makeGen () {yield 42;} const G1 = makeGen (); const G2 = makeGen (); g1.next (); / {value: 42, done: false} g1.next (); / / {value: undefined, done: true} g1.next (); / / No way to reset thisdispensg2.next (); / {value: 42, done: false}. Const G3 = makeGen (); / / Create a new generatorg3.next () / / {value: 42, done: false} Infinite sequence

Iterators represent sequences, a bit like arrays. So, we should be able to represent all iterators as arrays, right?

However, it is not. Arrays need to be allocated immediately when they are created, while iterators are delayed. Arrays are urgently needed because creating an array of n elements requires you to first create / evaluate all n elements in order to store them in the array. In contrast, iterators are lazy because the next value in the sequence is created / calculated only when it is used.

Therefore, an array representing an infinite sequence is physically impossible (we need infinite memory to store infinite items! The iterator can easily represent (rather than store) the sequence

Let's create an infinite sequence from 1 to positive infinite numbers. Unlike arrays, this does not require unlimited memory, because each value in the sequence is lazily calculated only when it is used.

Function * makeInfiniteSequence () {var curr = 1; while (true) {yield curr; curr + = 1;}} const is = makeInfiniteSequence (); is.next (); {value: 1, done: false} is.next (); {value: 2, done: false} is.next (); {value: 3, done: false}. / / It will never end

Interesting fact: this is similar to the Python generator expression vs list understanding. Although the two expressions are functionally the same, the generator expression provides a memory advantage because the evaluation of the value is delayed, while the list understanding is to immediately evaluate the value and create the entire list.

This is the answer to the question about how to understand Generators in JavaScript. I hope the above content can be of some help to you. If you still have a lot of doubts to solve, you can follow the industry information channel to learn more about it.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report