Learn about asynchronous generator in JavaScript

Source: Internet
Author: User

This article and everyone to share is mainly in JavaScript asynchronous Generator related content, come together to see it, hope to learn JavaScript help you.

asynchronous generators and asynchronous Iteration have arrived ! this is wrong. , They're still here .  Stage 3 , which means they are very likely to JavaScript will be released in a future version. Before they are released, you can use Babel to work on your project using the recommended content that is still in Phase 3 .

The Web site is basically a decentralized application, because any language modification will have a permanent effect, so all future versions will need to be backwards compatible. Therefore, being added to the ECMAScript standard feature, it must be very reliable, and its syntax needs to be elegant.

with this in mind, we hope that asynchronous generator and iterators can significantly affect how we build future code, but also solve the current problem. Let's begin to understand how asynchronous Generator works, and what problems it will encounter in our formal development.

Summary : How does asynchronous generators work?

In short, asynchronous generators is much like the normal generator function, but it can yield Promises.

generally speaking, the normal generator function is basically a collection of iterators and observer patterns. Generator is a function that can be aborted, and you can do this step by calling . Next () . You can output content from generator multiple times with . Next () , or through . Next (Valuetopush) To pass in the parameter multiple times. This bidirectional interface allows you to perform both iterators and observer functions in a single syntax !

of course Generators also has its drawbacks : It must return data immediately (synchronously) when calling . Next () . In other words, the code needs to get the data when it calls . Next () . It is possible to generate new data when generator is needed, but there is no way to handle iterating an asynchronous (or temporary) source of data, and they need to take control of the next time the next data is ready.

the WebSocket message mechanism is a good example of getting data asynchronously. If we have received all the data, then of course we can traverse them synchronously. However, we may also encounter that we do not know when the data will be received, so we need a mechanism to wait for the data to be received after the completion of the traversal. Asynchronous Generators and asynchronous iterators allow us to do this.

Simply put:the generator function is suitable for situations where the data can be controlled by the user, and the asynchronous generators is suitable for allowing the data source to control itself.

A simple example : generate and use asyncgenerator

Let's use an example to practice our async scenario. We need to write an asynchronous generator function that repeats the wait for a random number of milliseconds to come up to a new number. In a few seconds, it may start generating around 5 numbers from 0. Let's start by creating a Promise to create a timer:

// Create a Promiseand resolves after MS var timer = function(ms) {

return new Promise (resolve = {

setTimeout (Resolve, MS);

});

};

Run timer (in) will return a Promise, and will be resolve in 5 seconds . Now we can write an asynchronous generator:

//repeatedly generate a number starting//from 0 after a random amount of timevar source = as Ync function\* () {

var i = 0;

while (true) {

await timer (math.random () \* 1000);

yield i++;

}

};

such a complex function can be written so elegantly! Our asynchronous generator function waits for a random time after yield and decreases the value of I. If we do not have asynchronous Generator, we can use the normal generator function as below, through yield Promises to achieve :

var source = function\* () {

var i = 0;

while (true) {

yield timer (math.random () \* 1000)

. Then (() = i++);

}

};

of course, there are some special cases and references that need to be dealt with, so it's best to have a dedicated function type! Now it's time to write the code ; because we need await operator, so we will create an asynchronous Run () function.

// gather all together var run = Async function() {

var stream = Source ();

for await ( let n of stream) {

Console.log (n);

}

};

Run ();//= 0//= 1//= 2//= 3//...

This is amazing, only a few lines of code. First, we run the asynchronous generator function source , which returns a special asyncgenerator object. We then Loop through the source with a for await...of syntax called " async iteration " the generated object.

But we can also improve it: Let 's say we want to output The number generated by source. We can output them directly inside the for Await...of loop, but we'd better " convert " The value of the stream outside the Loop , Like using . Map () to convert the values in the array. It is so simple:

//Return A new async iterator that applies a//transform to the values from another async generatorvar map = Async function\* (stream, transform) {

for await ( let n of stream) {

yield transform (n);

}

};

and then we just need to go Run () add a line of code to the function:

//Tie everything together

var run = Async function() {

var stream = Source ();

+//Square values generated by source () as they arrive

+ stream = map (stream, n = n \* n);

for await ( let n of stream) {

Console.log (n);

}

};

when we run Run () it will output:

//= 0//= 1//= 4//= 9//...

how touching! But it's just a little bit overqualified for calculating numbers.

Intermediate Example : using asynciterator (asynchronous iterator) in WebSockets

we usually listen by binding events. WebSocket 's data:

var ws = new WebSocket (' ws://localhost:3000/');

ws.addeventlistener (' message ', event = =}

Console.log (event. Data);

});

But if you can put WebSocket information as stream, so that we can "iterate" the information above. Unfortunately,WebSockets does not yet have the functionality of an async iterator, but we can do it ourselves with just a few lines of writing. Our run () function probably looks like this:

//Tie everything togethervar run = Async () = {

var ws = new WebSocket (' ws://localhost:3000/');

for await ( let message of WS) {

console.log (message);

}

};

now for that Polyfill. You may recall Chris Aquino ' s blog series The content to be written in, an object to use For...of cycle, must have The symbol.iterator property. Similarly, an object must have a symbol.asynciterator property if it wants to use a for Await...of loop . Here is the specific implementation:

//ADD an async iterator to all WebSockets

Websocket.prototype[symbol.asynciterator] = Async function\* () {

while (this. ReadyState!== 3) {

yield (await oncepromise (This, ' message ')). data;

}

};

This asynchronous iterator waits for the message to be accepted and then WebSocket Returns the data Property of the messageevent to yield . The oncepromise () function has a bit of black tech: it returns a Promise, which is resolveswhen the event is triggered. Then immediately remove the event listener.

//Generate a Promise that listens only once for an eventvar oncepromise = (emitter, event) =

return new Promise (resolve = {

var handler = (... args) + = {

Emitter.removeeventlistener (event, Handler);

Resolve (... args);

};

Emitter.addeventlistener (event, Handler);

});

};

This may seem a little inefficient, but it proves that WebSocket 's information reception can be implemented with our asynchronous iterators. If you have a running WebSocket service in http://localhost:3000 , you can listen to the traffic by calling run () :

Run ();//= "Hello"//= "sandwich"//= = "otters"//...

Advanced Example : rewrite RxJS

now is the time to face the final challenge. reactive function Programming (FRP) is used extensively in UI programming and JavaScript , RxJS is the most popular framework for this type of programming. RxJS Model event sources such as observable-- They want an event stream or a lazy array, They can be processed by map () and filter () in a similar array syntax .

since FRP complements the non-blocking concept in JavaScript, and the class RxJS API is likely to be added to the in a future version of JavaScript. At the same time, we can use asynchronous generators to write our own RxJS -like functionality, which only requires Line Code. Here's what we want to achieve:

1. Listen for all click events

2. Filter Click events to get only The events that clicked on the anchor tag

3. Only allow distinct clicks for different clicks only

4. Map Click events to click Counters and click events

5. Only one click can be triggered per 500ms

6. number of clicks and events to print

7. These problems are RxJS solved, so we will try to re-implement them. Here is our implementation:

//Tie everything togethervar run = Async () = {

var i = 0;

var clicks = streamify (' Click ', Document.queryselector (' body '));

clicks = Filter (clicks, E = e.target.matches (' a '));

clicks = Distinct (clicks, E = E.target);

clicks = Map (clicks, E = = [i++, E]);

clicks = Throttle (clicks, 500);

Subscribe (clicks, ([ID, click]) + = {

Console.log (ID);

Console.log (click);

Click.preventdefault ();

});

};

Run ();

to make the above function work, we also need 6 functions: streamify () , filter () , distinct () ,map () , throttle () and subscribe () .

// put all event emitter into a streamvar streamify = Async function\* (event, Element) {

while (true) {

yield await oncepromise (element, event);

}

};

streamify () is like a WebSocket asynchronous iterator: oncepromise () is used . AddEventListener () to listen to the event once and then resolves Promise. through While (true) Loop , we can always listen to events.

//Only pass along events meet a conditionvar filter = Async function\* ( Stream, test) {

for await (var event of Stream) {

if (Test (event)) {

yield event;

}

}

};

filter () will allow only events passing through test to be yieldmap () is almost the same :

//Transform every event of the streamvar map = Async function\* (stream, Transform) {

for await (var event of Stream) {

yield transform (event);

}

};

Map () can simply transform events prior to yield. Distinct () shows one of the powerful features of asynchronous generator: It can save local variables !

var identity = e + = e;

// allow only events that are not the same as the last one through var distinct = Async function\* (stream, extract = Identity) {

var lastval;

var thisval;

for await (var event of Stream) {

Thisval = Extract (event);

If (thisval!== lastval) {

lastval = thisval;

yield event;

}

}

};

Finally, the powerful Throttle () functions and distinct () very much like this: It records the time of the last event and only allows more than the last yield event passes for an event that determines the time.

// only events that exceed the last event determination time are allowed to pass. var throttle = Async function\* (stream, delay) {

var lasttime;

var thistime;

for await (var event of Stream) {

thistime = (new Date ()). GetTime ();

if (!lasttime | | thistime-lasttime > Delay) {

lasttime = thistime;

yield event;

}

}

};

we've done so much, and finally, we also need to print out each click event and the current number of times. Subscribe () has done some piecemeal things: it runs at every event loop and executes callback, so there's no need to use yield.

// each event arrives with a callback function called var subscribe = Async (Stream, callback) = =

for await (var event of stream) {

Callback (event);

}

};

Here we have written a functional pipeline of our own reactive type!

you can be in here get the code and essentials to all the examples.

challenges

Asynchronous Generators is so elegant. While the generator function allows us to go back to the data from the iterator, asynchronous generators allows us to iterate " push " come over the data. This is how good an abstraction of an asynchronous data structure is. Of course, there are some caveats.

First, the ability to add support for Await...of to a objects is somewhat coarse, unless you can avoid using yield and await . In particular, using . AddEventListener() to convert anything is tricky because you can't use yield in a callback Operation :

var streamify = Async function\* (event, Element) {

Element.addeventlistener (event, E = {

// This will not work because yield

// can not be used in a common function

yield e;

});

};

Likewise, you may not be able to . ForEach () and other function-type methods are used in the yield . This is an inherent limitation because we cannot guarantee that yield will not be used after the generator has been completed .

to get around this problem, we wrote a oncepromise () function to help us in the group. Putting aside some potential performance issues, it is important to note that Promise callbacks are always executed after the end of the current call stack. On the browser side, the callbacks that run Promise like microtasks are not problematic, but some Promise Polyfill will not run callback until the next event loop is run . Therefore, calling the . Preventdefault () function is sometimes ineffective because DOM time may have bubbled up to the browser.

JavaScript now has multiple asynchronous stream data types: stream , Asyncgenerator, and last Observable . Although all three are " push " data sources, there are some subtle semantic differences in handling callbacks and controlling underlying resources.

Source: Zhong Cheng translation

Learn about asynchronous generator in JavaScript

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.