 I'm going to talk about async iterators and how it can change streaming systems in the future. So about me, I'm Steven, hello. I'm on GitHub and Twitter and we're going to talk about some things. The intro, some of you might have seen Matteo's talk earlier, so you've already maybe seen a bit of intro to async iterators, so I won't spend too much time on that one. Also going to show off some use cases, some more complicated use cases for async iterators. Talk about a bit of the pitfalls of using them and do a little performance demo. Async iterators, it's, it matches the async function system and the generator function system into one thing, special signature, you've got the async symbol and the asterisks, so you can await stuff and you can yield stuff and just returns an iterator that you can do four-weight loop over. And it's also internally an interface that's just promised, like promise returning next function that resolves to an object with a value and a flag indicating whether it's done or not, and objects that have this async iterator, like symbol dot async iterator function that returns an iterator can be consumed by four, like four or eight of loops. So get to the, get to the examples. So streams are actually async iterators, so you can just take any quick read stream and just four-weight over it, like right now this already works, so just go and do it. So with that, like you can do a simple concat stream type thing, but like this, like there's a bunch of modules in the user land right now to do like stream concat that are like five times the length of this to do the same thing, so it makes things easier. And you can also do stream transformations. I'm going to be soon working on a pull request that's in NodeCore, there's a function called pipeline, which you might see later in the presentation that replaces the like stream dot pipe. So you just call pipeline, it looks basically the same as this, you just give it a bunch of streams. This is my user land thing using async iterator pipe, which takes just any stream the same as pipeline does and transform streams, but it can also take the async generator functions in place of a stream, which just looks like that. So it's just a function that receives another iterable and produces a new one. So this iterable can be like an existing stream or another one of these and you can just channel these together. The nice thing about async generator functions is that you can actually just like call these nested and pass the values into each other, and that's basically what this pipe model does when it sees that it's not a stream. And so we're going to show something a little more complex this time, because this other one is just uppercasing a stream that doesn't really do anything to notable. So let's try parsing a CSV file and converting that to a new line to limit a JSON. So we have a CSV file, we just do create read stream on that, and then we have to do line split. So we want to start with an empty buffer and just as we receive chunks from the read stream, append those chunks to an intermediate buffer that we're working with, and continuously watch that until we see a new line character, which is just looking for the character code for it instead of the string, so I don't have to stringify this, and it's just looping over as long as there is a new line in here, then it will slice up to the point of that new line and yield that chunk and then move the pointer like resetting that buffer. And so this will just keep like loop until I have a new line and then emit that and just keep doing that, and then eventually you might have no new line terminator at the end of the file, so just yield if there's anything left. And now that we've split it into lines, we need to do something with those lines, which is translate that as CSV. So the first line in CSV is just the key names, so we can loop over each line and just split the first line into an array of the keys, and then the next, for every subsequent line, the position in the array maps to the key position in the key list, so we just make a data object out of that and then yield that, and then JSON stringify, so it's pretty easy to do. And so now we can pass that to standard dots. We've managed to do this fairly complicated task of taking CSV file and converting that to new line delimited JSON in like 50 lines of code, which with streams this would be a bunch more complicated. And so that pipe module is actually just this, like that's the entire module. It has this for handling streams, which as I said before, a stream is just an iterable, so you can just iterate over things, and this handles a stream on, like if it's targeting a stream, then you can just iterate over the iterable source, whether it's a stream or not, and just write into the stream, and when you're done the loop. And then if it's function, you can just hand it the source directly, just create this nested call thing, and then all we have to do is just detect whether something is a stream and pick which side of the logic we want to do, and then you can just do, like, reduce all the targets so you have, like, in the pipeline the first thing is the source, and then you're just, like, connecting each thing in the pipeline to the next thing, so you just have each target gets reduced into the source. So another cool example is HTTP servers. So typically you'll have, like, an HTTP app that you have, like, HTTP create server with, like, a handler to receive each request that comes in, but instead you can use HTTP iterator and just do create server without giving a callback, and you can actually do that normally and attach a, like, unresponse event instead, because that's actually what it's just doing internally, but this module will actually do that internally and convert that into an asynchronous iterator, so you can just for await over requests, and there's some kind of neat things you can do with this sort of pattern, like, pushing all of this into a queue and then, like, awaiting if you have, like, too many concurrent connections or something, like, you might want to throttle or things like that, so you can, like, push stuff into a queue and handle it elsewhere instead of, like, handling it all in this callback. It just gives you a little bit of extra controls, and so this is the entirety of HTTP iterator. It's a module called Channel Surfer, which provides, like, go, like, channels, so you create a channel and listens to, as I said, the server on request event and just passes the request and response into the channel and then closes the channel when the server closes, and that's it, so we'll get into more complex example now, so a lot of, like, APIs will have, like, paginated API, and they might, like, have their pages, like, nested in things, and it can be kind of awkward if you're, like, trying to deal with something like that. If you want to, like, interact with, like, every record in a system or something like that, then you have to, like, build a whole bunch of logic on top of logic on top of other logic that just makes it complicated to interact with, you know, something like that, so let's just consider, like, we have load page, just pretend this is, like, fetching from the network or something. It gets a page by the page number, so we can convert that, that's a page number getting function into an async iterator pretty easily by just having this, and as long as load page returns a, like, truthy value, then it will keep going in the while loop and yielding pages, so whenever it hits an empty page, then it stops. But each of these pages looks like this. It doesn't give us the items, it gives us the page. What we want is actually, like, a list of the items, so we can just convert one iterator into another iterator by just wrapping the pages iterator in one that loops over each page that it receives from that and emits each item from each of those pages, and so we can reduce this, like, what would normally be a fairly complicated system of interacting with this, like, whole paginated API and just turning that into an iterator that we can just loop over every item and, like, stop when we want to stop or just loop over everything as if it's just, like, a single iterable thing, which is pretty nice. And then another interesting application is batching. You might have, like, a giant sequence of things you want to break up into, like, groups of, like, process 10 items at a time or something like that. So here we have an infinite sequence of numbers. This will just keep looping forever. Every, like, 10 milliseconds it'll give you the next number. So for batching we want to be able to take, like, a certain number of items out of the iterator and process each of those, like, in that batch. So there's some interesting stuff in here that we're using the, like, iterator.next function instead of, like, a 408 loop here and then, like, a breaking after, like, n iterations. And the reason for that is that 408 of will fully consume an iterator even if you try to break out of it. So if you try to, like, hand the same iterator into, like, take n items out of it and then, like, a wait for that and then take another n items out of that, if you consume the whole iterator then it's gone. So, yeah, that's batching the while loop and the await iterator next. And when you're interacting with the, like, underlying iterator interface you have to check the, like, done flag and yield the value, not, like, yield the object. And so this might look a little bit familiar because this is actually effectively the same thing as the stream concat that I showed you earlier. It just doesn't do a buffer concat. So this is interesting in that, like, you can actually, like, abstract away the whole, like, stream concat thing entirely, like, don't even have a module that does that, just have a module that turns it into an array and then hand that into the buffer concat function and it works the same. So you can make this more generic thing and it's just easier. So now that we have those pieces in place we can make the batch function. So it just takes an iterator and it takes a page size and it will take a page of that size out of that iterator, convert that to an array and then as long as there is items to be yielded then it'll just yield that page. So we can do something, like, if we have infinite sequence of numbers we can take batches of that and sum the batches or things like that. But there are some interesting pitfalls to consider with async iterators though. So I've already mentioned this one which is, like, for a weight of loop fully consumed it's an iterator. There's some other interesting ones as well. A really weird one to wrap your brain around is that microtasks are a higher priority than everything else in the event loop. So what this means in practice is that you can do some things, like, in here, we have this function that we want it to just infinitely loop over and, like, await some task and we have this timeout here that just sets a flag to tell it, like, when it reaches this timeout just, like, set the flag to stop running the loop. So do you think this works? No. It does not because there's a weird thing in async await that you can actually await a non-promise and it's just will, like, await, like, it'll yield the value. It's, like, you can just do, like, await one and it'll give you one and it won't actually defer, well it will defer to the microtask queue but it will be immediately resolved and that particular property is a problem in that the microtask queue, basically the queue is in node, like, will step down to the next tier when the current tier has drained. So if the microtask queue every time it has a tick adds another task to the microtask queue it'll just keep cycling through the microtask queue forever and will never actually reach anything else. So the set timeout will actually never get reached because it's a lower priority queue. So it's a little bit of a weird issue but just realize that microtask are higher priority and you might have to debug that. This is kind of connected to that. If any of you remember the whole Zalgo issue and the DZalgo module that came out of that, in, like, early days of node there was, like, lots of people would, like, write async stuff that, like, was sometimes async. Like, you might have a function that, like, might do, like, an FS read file or something but it might cache that data and then, like, the next time you call the function it has that stored in memory so it's just going to call the callback immediately but it'd sometimes call it in the same tick instead of the next tick so, like, you'd have, like, logically your code would, like, call this async thing and then you might have something below it that that would, like, on the first run that would happen first and then the callback would happen but then that would switch around because it was calling the callback in the same tick and it just, like, makes your code super confusing and non-deterministic so the DZalgo module was created to ensure that things are always async and this is basically the equivalent in async iterators. We have to do this again, apparently. So you just await another promise that's, differs to set immediate which is at the bottom of the, the, like, tiers of Q priorities. So this will say, like, once everything else is done then jump back to this again. So I also have a bit of performance demo. So there's been, like, a lot of people have kind of avoided async iterators and, like, related to that promises because of the myth that promises are slow which, at one point, had some truth to it but there's been a huge amount of effort that's gone into optimizing promises over the last several years that they're actually quite performing at this point. So I'm going to show a little demo. So I'll just show you the code for it. So on the left side we have how to implement the, like, just uppercasing stream and on the right side we have the equivalent code in async iterator. So this just will take an infinite stream of stuff and uppercase it. Let's see, oops, my node module is missing. That's not good. I'll just install that in the background and install for a minute or try to, I think it's all cached locally so it should work without a connection. So let's walk through the code. So the stream version just, you push the uppercase in and then do the set immediate because you need to be a responsible async participant. And this is doing effectively the same thing because of Disaligo reasons. Demo doesn't seem to want to run right now. I'll show off some of the code then instead. So this is just the CSV to JSON. This is optimized slightly from the other one. So it does the line splitting and CSV parsing all in one. And it has some helpers to handle the converting the keys and stuff. But I was kind of hoping to show the demo but it doesn't run around. So I did some performance testing basically. And when running the uppercase, like the uppercasing demo, it's fairly naive code so it's not going to have any significant performance difference. But the CSV to JSON has like a little more meat to it. There's a little more stuff there that's like more real world kind of demo. And the code from the async iterator actually is about 15% faster than streams are and about 40% less memory usage. So you can actually use async iterators now and get as good or better performance than streams if you know how to use them properly. That's basically the end of that. So go forth and async iterates. Sorry my demo didn't work great.