16 ms·
Why would anyone need JavaScript generator functions?
- sieabahlpark 4y ago
- ramesh31 4y agoI classify generators along with switch statements and while loops in JS. Perfectly fine to use if you know what you’re doing, but generally a code smell that belies further non-idiomatic code.
- jalino23 4y agoI prefer switch now over longer if else, and find my code to be much simpler. I stopped following most of what clean code recommends such as using polymorphism as a switch case or if else.
- kevinmchugh 4y agoMany times, switch is used when an object would suffice
- whilenot-dev 4y agoI'm sorry, but in my decade of JavaScript I never read anything more cryptic than this. What is this supposed to mean?
- lolinder 4y agoNot OP, but I've often seen cases where the same set of strings or enum values are used in switch statements in disparate parts of the code base. While each switch is supposed to be checking the same set of values, they invariably end up getting out of sync and checking different subsets of the actual possible set of values, leading to bugs. Some languages support exhaustive switches (typescript has a way to do this), but oftentimes the better solution is to consolidate all of these switches into an object, or a set of objects that share an interface. That way all of the switch statements become function calls or property accesses, and the type checker can warn you at time of creation of the new object if you're not providing all of the functionality the rest of the code will require.
- couchand 4y agoAnd of course, there is no free lunch. You run smack into the expression problem here. The question is: do you have more places that check your conditions than types of options? Are you more likely to add a new check (and need to verify exhaustiveness) or add a new option (and need to modify every check)?
- kevinmchugh 4y agoA switch statement allows you to have different behavior for different inputs, but often all that's needed is different output data. If you have to map, say, country codes to country names, writing a long switch statement of case "US": name = "United States of America" break Is going to suck. An object (in js, an associative array more generally) will be simpler. It's not always quite so obvious as that example.
- aastronaut 4y agoThanks for the example! I guess we consider switch/case in different situations then. I usually make use of switches in simple mappings and switch/case seems more readable and idiomatic to me there: type CountryCode = | "CH" | "US"; function nameFromCountryCode(countryCode: CountryCode): string { switch (countryCode) { case "CH": return "Switzerland"; case "US": return "United States of America"; default: // exhaustiveness checking ((val: never): never => { throw new Error(`Cannot resolve name for countryCode: "${val}"`); })(countryCode); } } ...instead of... type CountryCode = | "CH" | "US"; function nameFromCountryCode(countryCode: CountryCode): string { return ({ 'CH': (() => 'Switzerland'), 'US': (() => 'United States of America'), }[countryCode] || (() => { throw new Error(`Cannot resolve name for countryCode: "${countryCode}"`); }))(); }
- mdaniel 4y agoI'll bite: why the anonymous function throwing an Error in the first snippet?
- aastronaut 4y agoI didn't lay out a bait =) It would also look more readable to me with a default return value. An exhaustiveness check just keeps your mapping functionally pure and the type checker can catch it.
- deleted 4y ago[deleted]
- ramesh31 4y ago>I'm sorry, but in my decade of JavaScript I never read anything more cryptic than this. What is this supposed to mean? Switches in JS are just implicit maps. const caseVal = 'case1'; const result = ({ 'case1': (()=> 'case1Val'), 'case2': (()=> 'case2Val'), 'case3': (()=> 'case3Val'), } [caseVal] || (()=> 'default'))() Has identical performance to a switch case and is far more idiomatic.
- aastronaut 4y agoThanks for the example! I assume the identical performance just comes from the optimization of the JIT, as allocating objects in the heap seems quite overkill for such a control flow. I only fall back to this when switch/case isn't available in the language, eg. in Python. Is this a thing in the JS community?
- svieira 4y agoIt's not in JavaScript, but see Unifying Church and State: FP and OOP Together for another example of the "switch / named functions" (or "Wadler's Expression Problem") described in a pretty intuitive way: https://www.youtube.com/watch?v=IO5MD62dQbI https://www.youtube.com/watch?v=IO5MD62dQbI
- aastronaut 4y agothere goes my evening, that's what I like about HN. thanks for the refs!
- deleted 4y ago[deleted]
- myvoiceismypass 4y agoTypescript supports exhaustive switches which are pretty powerful, especially when working with proper union types and the like.
- adam_arthur 4y agoI find using objects mapped as switchKeyToFunction to be more idiomatic and readable. e.g. shapeToDrawFn[shape] But to each their own
- phs2501 4y agoWill the compiler check that you have an exhaustive set of keys in your definition of `shapeToDrawFn`? (Not a rhetorical question, I don't know.)
- the_other 4y agoRedux-sagas[0] makes great use of generators. I found it a fantastic tool, if you're already in the redux ecosystem, and have an application that sprawls enough to benefit. It's great when you have to manage multiple process lifecycles. A single "saga" can summaries an entire service lifespan in just a few lines of code. Good candidates include a socket connection, a user session, analytics etc. I desperately want to use it with my current project (streaming video) but our code's too mature to introduce such an architectural change. The downsides are: - a quirky syntax that needs learning, and is of the "loose with semantics" style - like Rails-eque REST's play with HTTP methods - it's hard to test (despite what the documentation claims). It's highly declarative, and such code seems hard to test. [0] http://redux-saga.js.org/ http://redux-saga.js.org/
- cageface 4y agoI had to rescue a saga based project that went badly off the rails and it was some of the hardest code I’ve ever had to debug in any language. Code flow was very difficult to reason about and forget about trying to use stack traces.
- tylerchurch 4y agoYeah I had to rescue a buggy React Native app where the devs put every backend call into sagas. It was a pain to debug and I’ve since almost entirely ripped sagas out. It’ll be a good day when I can delete it as a dependency.
- pgorczak 4y agoAsync generators similarly open up different ways to program complex / multi step user interactions. Instead of creating “state machine” objects that mutate on every input, you can have async functions aka coroutines that iterate over user inputs, making the flow of an interaction much more explicit.
- isakkeyten 4y agoBe careful. You will summon acemarke with this comment.
- drawfloat 4y agoNot sure about the timtam example. Couldn't this: const flow = (...fns) => x0 => fns.reduce( (x, f) => f(x), x0 ); function slamTimTamsUsingFlow(timtams) { return timtams .slice(0, MAX_TIMTAMS) .map(flow( biteArbitraryCorner, biteOppositeCorner, insertInBeverage, drawLiquid, insertIntoMouth)); } Just be written as: const slamTimTams = timtams => timtams .slice(0, MAX_TIMTAMS) .map(timtam => timtam.biteArbitraryCorner() .biteOppositeCorner() .insertInBeverage() .drawLiquid() .insertIntoMouth()); Feels much more concise and readable than the flow example
- jzig 4y agoYup. I found this article difficult to read.
- zwkrt 4y agoYes. Furthermore I am not really even sure what the motivation was in the first place. I have found generators to be most useful in my career in exactly one circumstance--when I want to abstract some complicated batching, filtering, or exit condition logic out of my main processing loop. As a specific example, generators are great for taking an input stream and returning the first 10000 packets matching '^foo.*' in batches of 10. In the article, it seems like we are just looping over a set of 11 cookies and doing exactly the same thing to all of them. Not sure what I'm missing. The stuff after that regarding infinite sequences is pretty good though!
- Jtsummers 4y ago> In the article, it seems like we are just looping over a set of 11 cookies and doing exactly the same thing to all of them. Not sure what I'm missing. You're missing that we're not just looping over a set of 11 cookies and doing exactly the same thing to all of them. We're streaming over a lazy sequence of cookies and doing the same thing to them until we stop (in the example when they hit 5 cookies eaten instead of eating them all and, presumably, becoming ill). Generators act like streams, and are useful in any place you might see this pattern: value, state = next_value(state) -- alternatively if we can mutate the state directly -- value = next_value(state) But they permit you a greater deal of flexibility about the way "state" mutates than a typical state structure might (or at least greater ease of use).
- qsort 4y agoThey were introduced when JS went through its "let's copy python" phase. They're kind of crippled because generators only really become useful if you have the equivalent of itertools to build a sort of iterator algebra. I love generator-based code in python but it's hard to replicate that without having the library too.
- WorldMaker 4y agoThat's a call out in the article, too. There's a Stage 2 proposal before TC-39 to add a bunch of useful library functions out of the box to the language. In the mean time there's an itertools.js "port" and also IxJS which mirrors RxJS (and .NET LINQ). (I've used IxJS to good effect in JS apps.)
- scotty79 4y agoBecause it's an awesome and easy way to create iterables. class FreakyCollection { [Symbol.iterator]() { return function*() { for //... // iterate over your freaky collection however you please yielding one element by one }(); }
- jitl 4y agoThis can be condensed slightly, you don't need the inner function literal. In Typescript: class MyCollection<T> { *[Symbol.iterator](): IterableIterator<MyCollectionEntry<T>> { for (const thingy of this.thingies) { yield { ... } } } }
- scotty79 4y ago... also custom and paremetrized iterators: class FreakyCollection { // ... *even() { // for ... iterate skipping the odd ones and yielding the rest one by one } *having(quality) { // for ... iterate and yield just the items that have some quality } and usage then is as simple as: for(let item of freakyCollection.having(42)) {
- apineda 4y agoI use them to iterate over structs in WASM memory that are neatly packaged as an "object", but are just getters into the current offsets.
- BigJono 4y agoAll you did was turn 15 lines of reasonably readable functional code (that, as another comment pointed out, can be done in 8) into 31 lines that were much harder to read.
- ravi-delia 4y agoThe price of using simple examples is measured in pages of pedantry
- javajosh 4y agoYou can kinda define generators with an arrow function, kinda: const foo = (function*() { yield 1; })(); console.log(foo.next().value); I've only written one generator in "real life" and ended up replacing it anyway: // A generator function that returns a rotating vector function* circlePath(stepsPerRotation = 60, theta = 0) { while (true) { theta += 2 * Math.PI / stepsPerRotation; yield [Math.cos(theta), Math.sin(theta)]; } }
- inbx0 4y agoThe main benefit of arrow function generators would be the inherited parent-scope this-binding they'd get
- javajosh 4y agoYeesh! I personally stay away from "this" as much as I can, in part because of this effect. I can't imagine using the arrow function's odd "this" behavior intentionally! Plus, I like pure functions a lot so I'd rather just pass in any state through a parameter, which is less error-prone too.
- int_19h 4y agoWhy is the arrow-function "this" behavior odd? It captures it from the outer scope, just like it does with all other local variables. This is exactly how it works in pretty much every other language out there. It's the old-style "function" behavior that always determines "this" at the point of the call that's odd, if anything.
- javajosh 4y ago>Why is the arrow-function "this" behavior odd? Because it behaves very differently from ordinary function "this" behavior. Your statement about "this" being defined at the function call site is just...wrong.
- 4y ago
- jfengel 4y agoGenerators are a fairly natural way to write recursive descent parsers. The alternatives are either to parse everything and return it in one big structure (which can be awkward for large documents) or to supply Visitors (which works, but often doesn't match your mental model). It's nice to be able to write "a lexer just yields a stream of tokens" and "expr ::= term op expr" as: function* expr() { x = term(); operator = op(); y = expr(); yield apply(operator, x, y); } Backtracking takes a little setup, but overall it's a very elegant way to write code. Not many people really need to write parsers, but even if you're using a black box from somebody else, it can be fairly elegant to use if it supplies it as an AST generator or result generator.
- kevincox 4y ago+1 There is a lot of code that is easier to write in "push style" where the code runs blocking and "pushes" results (either to a queue, a callback or a result array that is returned at the end) but it is better for the consumer if it is "pull style" where they can process items as they receive them and can do streaming/incremental parsing. Language supported generation functions/courtesies make it very easy to write in a "push style" while being possible to call in the "pull style".
- girvo 4y agoAn example of what you’re describing in a different domain, is how we are using channels between tasks in FreeRTOS in our embedded firmware. Other tasks just push commands or data into a channel that the consumer can pull at is leisure (modulo the size of the channel of course, we have fun constraints!)
- triyambakam 4y agoThat's really cool. Do you have any further more detailed examples of writing such a parser using generators? It's very coincidental because I've been learning about this topic recently.
- shadowofneptune 4y agoIn my own lexer in JS, I considered using a generator but instead used a class with an index property. In your experience, what advantages does a generator have over that approach?
- jitl 4y agoYou can use One Weird Trick with generator functions to make your code "generic" over synchronicity. I use this technique to avoid needing to implement both sync and async versions of some functions in my quickjs-emscripten library. The great part about this technique as a library author is that unlike choosing to use a Promise return type, this technique is invisible in my public API. I can write a function like `export function coolAlgorithm(getData: (request: I) => O | Promise<O>): R | Promise<R>`, and we get automatic performance improvement if the caller's `getData` function happens to return synchronously, without mystery generator stuff showing up in the function signature. Helper to make a function that can be either sync or async: https://github.com/justjake/quickjs-emscripten/blob/ff211447f403031cc7c2dcf484173a177cecf7a9/ts/asyncify-helpers.ts#L18-L26 https://github.com/justjake/quickjs-emscripten/blob/ff211447... Uses: https://cs.github.com/justjake/quickjs-emscripten?q=yield*+language%3ATypeScript https://cs.github.com/justjake/quickjs-emscripten?q=yield*+l...
- athanagor2 4y agoI used a very similar pattern in a web app, in which the user creates objects by clicking in different places. There is a state built by each click, and it is convenient to just go back one or several steps behind sometimes. So you have something like const input1 = yield // arbitrary side effects, UI updates etc. const input2 = yield // ... Each time the user makes an input, it is accumulated in a list, and sent back to the generator function. When the user decides to cancel the last step, a generator is recreated and rerun, with each yield sending back the user input that was stored in the list, except the last one. This requires writing the generator function in a particular way (you have to avoid setting "external" state), but it works and is more flexible than automata, I think.
- jupp0r 4y agoThe problem with this is that it breaks some tooling (async stack traces in Chrome dev tools for example).
- c-baby 4y agoThis seems a lot more complicated than something like this: if (!(value instanceof Promise)) { value = Promise.resolve(value) } And then you write the rest of your code as if it is async.
- whycombinetor 4y agoStrictly speaking, there's no reason anyone "needs" generator functions. Instead of pausing the function execution at each `yield` statement, you can just write a regular (non-generator) function that returns a struct including all the local variables used in the function, and repeatedly call the function with the returned struct as the first argument (or `reduce`). Admittedly this is just writing the exact mechanism of yield in a different way just to avoid using `yield`, but that's the point, it's not necessary to have it built in to the language.
- stevewatson301 4y agoFor anyone trying to get a better idea of what parent means, the PHP Generators RFC[1] shows the equivalence between generators and iterators. Iterators are what OP means by "struct including all the local variables used in the function". [1] https://wiki.php.net/rfc/generators https://wiki.php.net/rfc/generators
- int_19h 4y agoIn a similar vein, closures are not necessary, since they can always be emulated by a global function with a data object to bundle captured state - which is still the usual way to do it in C, for example. But this sort of thing gets real awkward real fast if you do it a lot. Same thing with explicit iterators vs generators.
- lmm 4y ago> they can always be emulated by a global function with a data object to bundle captured state Only if you already have the infrastructure in place to pass that captured state through to where it's needed. For a C example, you can't (safely) use an "emulated closure" with qsort (e.g. if you want to write a function that takes a list and an integer, and sorts the list modulo that integer), because you have no way to pass the data object through.
- irrational 4y agoMy main takeaway from this is that I need to find a place to buy TimTams in the USA.
- bdickason 4y agoSame - I think this might be a clever timtam add disguised as a programming article !
- WorldMaker 4y agoAt least in Kentucky both Kroger and Target have them randomly in the imports section of the cookie aisle if you look for them. (Depending on supply chain presumably.) In Kroger it's often the hard to see/hard to find top shelf. They rarely get the fun or the especially good flavors, but they often have the originals. I don't think Kentucky has an especially large Australian influence, so I assume that they distribute Tim Tams somewhat broadly nationally, but I don't know.
- Waterluvian 4y agoPython is where I developed my (naturally flawed but possibly useful) mental model for generators: you are "pulling" data out of them. Instead of pushing data into a processing grinder, watching the sausage links pour out the other side, whether you're prepared or not, you're pulling each sausage on-demand, causing the machine to move as a consequence of wanting an output. I'm sure smarter people appreciate generators more than I do. They're useful for co-routines and other wizardry. But I personally just find the mental model more useful in some cases, especially knowing it keeps memory and CPU usage more in-line with my needs. Doubly especially if my generator may not have an upper bound.
- throwawaymaths 4y agoI mean that's fine but the language decision that makes my small brain break is "why call it a function, instead of calling it a completely different name to represent the completely different things that it is"?
- Waterluvian 4y agoI never thought of that, and now I'm asking the same question. Perhaps there's history or mathematical explanations to this. But, yeah, "A function that can temporarily pause itself and be returned to later" is a possibly confusing overloading of the concept of a function.
- throwawaymaths 4y agoIt makes sense in a low level language like zig, because an async functions there truly is a function frame (in the sense of set up a stack and save your registers to be popped off of it at the end) but in high level languages - and even rust, if my understanding of how async works in rust - it's different and calling it a function seems incorrect.
- throwawaymaths 4y agoAnyone care to explain their downvotes?
- ravenstine 4y agoI initially liked the idea of generators, but after years of trying to find ways to apply them, I just haven't found a use case where they were more sensible than using existing logic and looping constructs. They could be useful where they would provide less overhead than constructing arrays for the same purpose, but that doesn't mean the same thing can't be achieved without either of those things. It's good in theory, but hasn't been useful to me in practice.
- koliber 4y agoImagine you have function that returns elements. In order to return each element, you need to do some time-consuming calculation. You don't know how many elements the users of your function will need. Some may need 13 items. Some the first 500. Others might be interested in knowing only the first item. Let's say that your sequence has a maximum size of 1000. If you were to return an array, you'd need to construct the full array each time, even if the code that calls your function only needs 1 item. Using a generator, you can write the code once, and it is performant across different use cases.
- malcolmstill 4y agoI could write this against a few other replies but I will write it here. Moreover, I don't think I'm going to say anything that hasn't already been covered by other people, but I am going to attempt to distil down the arguments. - The benefits of generators are, in a large part, the benefits of using iterators - What are the benefits of using iterators? As you say, one benefit is that calling `next` on an iterator performs a single unit of work getting the next value. This let's you avoid e.g. allocating intermediate arrays, let's you do infinite streams, etc. Compare that to calling `map` on a list...you have to consume the entire list. - A second benefit of iterators is that of scope. When I call `next` on an iterator I get the next value in the scope of the caller. This is particularly useful in a language with function colouring, because use of the `next` value can match what is required of the caller. E.g. the caller may want to await a function using some field of the `next` value and this is totally fine. Compare that to calling `map` with a lambda and wanting to `await` inside the lambda...the problem is you are now in the wrong scope and can't `await`. - So where do generators come in? Well they are just syntactic sugar that will generate the state machine that you would otherwise have to implement by hand in your iterator. In that sense you don't need generators at all... - BUT, with generators you can do things that would technically be possible with iterators but would be so clumsy to implement (I'm thinking here of coroutine libraries) that having them as a distinct feature makes sense.
- jtolmar 4y agoIn games, you can use a generator function for stuff that's supposed to take multiple frames. Saves you from either having to write a whole state machine system, or extracting all the state for every multi-frame action out into the object.
- Dylan16807 4y agoYeah, for things split across animation frames it's great to have the ability to trigger .next() exactly when and where you want to, while still writing straight-line stateful code. Await lets you do the latter, but not the former.
- int_19h 4y agoWouldn't it be better to use async/await for that?
- heloitsme22 4y agoHave you ever seen the rain?
- teucris 4y ago> Repeat until there are no more Tim Tams, or you feel physically ill. Similar to consumption of generator functions.
- Pandavonium 4y ago> Arguably, Australia’s greatest cultural achievement is the Tim Tam. After eating half a packet with my cuppa this morning (and feeling somewhat queasy for it), I can confirm timtams are one of our finest accomplishments.
- vanderZwan 4y agoCombine them with a decent coroutine library and you can write relatively straightforward singlethreaded concurrency code. Ramsey Nassr has been exploring that: [0] https://merveilles.town/@nasser/107892762993715381 https://merveilles.town/@nasser/107892762993715381 [1] https://jsbin.com/mupebasiro/edit?html,console,output https://jsbin.com/mupebasiro/edit?html,console,output
- whatwherewhy 4y agoWhat's the reasoning for using this over promise and/or async?
- vanderZwan 4y agoFair question! Well, look at the part in the code example in the second link where it goes: // then either cancel, drag/drop, or click yield* coro.first( /* ... */ ); In this example that function takes three generator functions. Whichever of the three yields a value first "goes through", and coro.first() then aborts the other two. The resulting code reads a lot like how you would describe it: "first detect a click on the rectangle, then either cancel the repositioning if escape key is pressed, move the rectangle if the mouse moves, or drop it if a click happens" The structure is a lot more like the "if/else" kind of control flow structures that most people are more familiar with. On top of that it's deterministic (technically, single-threaded use of things like setTimeout also are but because of how you would structure this it is easier to reason about). Another way to look at it would be to say that this way of expressing things aligns better with solving the problem in terms of state machines (and with UI that often is quit a nice approach). This is know as the structured synchronous concurrency paradigm and it's actually quite nice for certain types of (singlethreaded) concurrency, especially complex UI events. Céu is a language that goes a bit deeper into this, as well as the Blech language (both targeting embedded contexts - button presses changing machine settings are places where FSM are a natural fit). http://ceu-lang.org/ http://ceu-lang.org/ https://www.blech-lang.org/ https://www.blech-lang.org/
- jbluepolarbear 4y agoI use generator coroutines pretty extensively in my game framework. It makes for some clean cooperative code that’s fun to write. https://github.com/jbluepolarbear/Bumble https://github.com/jbluepolarbear/Bumble
- no_wizard 4y agoIf generators were treated as first class concerns in JS, they'd be so much more useful, but for them to be useful today you have to do alot of work to make it so in my opinion. We need iterator helpers to make them more useful built in to the standard language.
- _greim_ 4y agoI've found generators to be a great way to keep separate concerns separate. Now you can have a function whose job is to figure out what the next thing is, then consume it eslewhere as a simple sequence. In the past I'd have a function accept a `visit(x)` callback, but then I had to invent a protocol for error handling and early cancellation between the host and callback. The ability to just `yield x` and have done with it is a breath of fresh air.
- rbalicki 4y agoFor a concrete example, Relay uses them to garbage collect data in an asynchronous fashion. If an update is detected during the course of a gc, it is aborted. https://github.com/facebook/relay/blob/main/packages/relay-runtime/store/RelayModernStore.js#L567 https://github.com/facebook/relay/blob/main/packages/relay-r...
- rezonant 4y agoI used generators to increase the performance of bitstream parsing by several orders of magnitude over using a promise based system https://github.com/astronautlabs/bitstream#generators https://github.com/astronautlabs/bitstream#generators
- gbuk2013 4y agoI'm a bit surprised that database query pagination isn't directly mentioned as one of the use cases. The (async) generator wraps the paginated calls to the DB and yields pages or individual documents / rows. It's about as vanilla-CRUD-API scenario as I can think of.
- inglor 4y agoWe've made every node stream async iterable, and we also support all the iterator helpers. If whatever you're using is a Node stream - this works and it exposes an async iterable stream :)
- jitl 4y agoAgreed! We do this in Notion's public API library: for await (const block of iteratePaginatedAPI(notion.blocks.children.list, { block_id: parentBlockId, })) { // Do something with block. } Implemented here: https://github.com/makenotion/notion-sdk-js/blob/90418939a90aa04f52e7bb9b15444cd77b29cc03/src/helpers.ts#L25-L57 https://github.com/makenotion/notion-sdk-js/blob/90418939a90...
- 8note 4y agoI really like using generators in test code for setting mock/stub. You can be really specific about how you want it to run, without having to memorize a new language of function calls that have to be triggered in specific orders
- sbf501 4y agoI use them for reading large files that need to be parsed. A token might require a complex decode, so the generator fires once per token, but keeps the state of the file structure and the parser. It greatly simplifies the code.
- AtNightWeCode 4y agoBlock /assets/book-bg.jpg or remove the background from body::before to make it readable. Article still probably about JS.
- munificent 4y agoMy favorite use case for generators (I was using C# at the time, but it applies equally well to any language with yield) was for implementing a turn-based game loop: http://journal.stuffwithstuff.com/2008/11/17/using-an-iterator-as-a-game-loop/ http://journal.stuffwithstuff.com/2008/11/17/using-an-iterat...
- mrozbarry 4y agoI have done something similar, https://github.com/mrozbarry/mob-turnbased-rpg/blob/master/src/game.js https://github.com/mrozbarry/mob-turnbased-rpg/blob/master/s...
- Frotag 4y agoYou can also implement context managers / try-with-resources via generators. Not that I've seen it in the wild or recommend it. I remember it being annoying to debug for some reason. https://stackoverflow.com/questions/62879698/any-tips-on-context-manager-similar-to-python-in-javascript https://stackoverflow.com/questions/62879698/any-tips-on-con...
- andrewstuart 4y agoI rarely use generators but used one the other day. I had some code that needed to deal out the next in a sequence of URLs, to functions that were triggered by events occurring at random times.
- oefrha 4y agoAsync generators should be a very natural interface for many event-based APIs, e.g. WebSocket.onmessage. Unfortunately converting a callback-based API to an async generator is highly nontrivial.
- michaelsbradley 4y agoEasily done with Ix: https://github.com/ReactiveX/IxJS/blob/master/docs/asynciterable/converting.md#creating-a-sequence-from-events https://github.com/ReactiveX/IxJS/blob/master/docs/asynciter...
- wefarrell 4y agoI do wish that JS had more builtin syntactic sugar around generators (and particularly async generators). They would be a lot more convenient and syntactically cleaner if most of the Array builtins could also be used for iterators. For async iterators the builtins would need to leverage the promise builtins as well, so you could to something like: const result = await asyncIterator.map(doSomethingAsync).reduce(asyncAggregatorFn) Without ever having to load all of your data into memory.
- mirekrusin 4y agoYou may be interested in: - https://github.com/preludejs/async-generator https://github.com/preludejs/async-generator - https://github.com/preludejs/generator https://github.com/preludejs/generator ...and example usage https://observablehq.com/@mirek/project-euler https://observablehq.com/@mirek/project-euler
- c-baby 4y agoYou can do something like that with rxjs.
- michaelsbradley 4y agoYou might be interested in what IxJS has to offer: https://github.com/ReactiveX/IxJS https://github.com/ReactiveX/IxJS RxJS is for observables. IxJS is for iterables. Or, a bit more accurately, as explained in the readme: IxJS unifies both synchronous and asynchronous pull-based collections, just as RxJS unified the world of push-based collections. RxJS is great for event-based workflows where the data can be pushed at the rate of the producer, however, IxJS is great at I/O operations where you as the consumer can pull the data when you are ready.
- bakkoting 4y agoThere's an active proposal which would let you do exactly that, going for stage 3 (ready for implementations) at the end of the month: https://github.com/tc39/proposal-iterator-helpers https://github.com/tc39/proposal-iterator-helpers
- swalsh 4y agoCan I ask what kind of Tea goes best with Tim Tams? Are we talking about just simple black Earl Grey here?
- Sniffnoy 4y agoI've used JS generator functions at work. Part of it is redux-saga, which has already been mentioned, but part of it is another use. We use generators to implement functions that can make requests to the caller for more information. So the caller f calls the generator function g, creating an iterator, and then calls iter.next(...) in a loop. If the result is not done then the return value is a request from g to f for more information, which f supplies. If the result is done then g has returned a value and f can use it. The reason we do this is actually an architectural one. In this case, the extra information supplied by f to g comes from an external network, and g lives in an internal library that we don't want making any network requests or having any networking dependencies. My boss came up with this solution, and so far it's worked out pretty well for us! Note that before we split things up like this, g did make network requests directly, so g was async and its code was full of awaits. After switching it to this new structure, the awaits just became yield*s. :) (And the actual network requests themselves became yields.)
- talkingtab 4y agothat sounds really interesting. I've been doing a bunch of things and the flow is where the flow is irregular- sort of weird state machines. Putting it into a generator kind of things seems worth a try.
- talkingtab 4y agoTransducers. See Richard Hickey's talk about transducers here: https://www.youtube.com/watch?v=6mTbuzafcII https://www.youtube.com/watch?v=6mTbuzafcII You can implement transducers in JavaScript using generators. It was somewhat mind bending but fun.
- vbezhenar 4y agoI used generator to iterate over AWS objects with automatic paging. Code was very compact and easy to read and write. Other approaches would be worse. I don't think I ever used it again.
- grose 4y agoI found async generators to be handy while porting a C Prolog interpreter to WASM/JS. A `for await` loop is a natural way to iterate over query results, and as a bonus you can use `finally` within the generator to automatically clean up resources (free memory). There are ways for determined users to leak (manually iterating and forgetting to call `.return()`) but I've found that setting a finalizer on a local variable inside of the generator seems to work :-). I can't say manual memory management is a common task in JS but it did the trick. The generator in question, which is perhaps the gnarliest JS I've ever written: https://github.com/guregu/trealla-js/blob/887d200b8eecfca8ed6e83cecd2ca384ee28d1c0/src/prolog.js#L77 https://github.com/guregu/trealla-js/blob/887d200b8eecfca8ed...
- nilslindemann 4y agoI like generators and using them in for (... of ...) {...} loops. Reminds me of Python joy.
- jongjong 4y agoI started using async generators years ago and using for-await-of loops to consume streaming data and be guaranteed that the processing order is maintained when there are async operations to be performed on each chunk of data. It's difficult to do in any other way; you'd need some kind of queue structure which would make code very difficult to read.
- kaleidawave 4y agoIt’s silly how the ‘async’ function modifier got a keyword before the function keyword and the generator modifier got a symbol ‘*’ after the function keyword…
- oneeyedpigeon 4y ago> Some people might find the Australian and British accents difficult to understand. Or even recognise; Graham Norton has an Irish accent, not British.
- kderbyma 4y agoPaging, Streaming, Loops, Coroutines between asynchronous systems, queues, and state machines, emulators, etc.
- nikeee 4y agoThe author mentions iterator helpers [1], which could make stuff easier. However, there is also a different proposal that touches different similar issue which might fix this too: The bind operator proposal [2]. Like the name implies, it allows to set the `this` for a function call. This opens the possibility to implement the common map/filter/reduce functions in a lazy manner _for arrays_. Taken from the samples, this could evaluate lazily on an array returned by `getPlayers()`. import { map, takeWhile, forEach } from "iterlib"; getPlayers() ::map(x => x.character()) ::takeWhile(x => x.strength > 100) ::forEach(x => console.log(x)); Of course, this could also be used for iterators. However, the binding operator is not very active any more. [1]: https://www.proposals.es/proposals/Iterator%20helpers https://www.proposals.es/proposals/Iterator%20helpers [2]: https://github.com/tc39/proposal-bind-operator https://github.com/tc39/proposal-bind-operator
- inbx0 4y agoThe pipeline operator [1] is a bit further along, although I'm not exactly holding my breath for it either. At least it currently has a champion. With pipelines import { map, takeWhile, forEach } from "iterlib"; getPlayers() |> map(%, x => x.character()) |> takeWhile(%, x => x.strength > 100) |> forEach(%, x => console.log(x)); 1: https://github.com/tc39/proposal-pipeline-operator https://github.com/tc39/proposal-pipeline-operator
- dimgl 4y agoSo I keep seeing Sinclair's blog posts both on /r/javascript, and this is the first time I see it on Hacker News. While I appreciate the effort needed to write one of these posts, I can't help but think about the amount of bad code that these blog posts are inspiring.
- az_reth 4y agoHalfway through the article I completely forgot that it was about JavaScript, and I could only think about Tim Tams
- sktrdie 4y agoGenerators are a fun use-case for bthreads since they allow to "cut" through other modules and control their execution: https://medium.com/@lmatteis/b-threads-programming-in-a-way-that-allows-for-easier-changes-5d95b9fb6928 https://medium.com/@lmatteis/b-threads-programming-in-a-way-...