└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Push Generator Proposal (ES2016) 2 | 3 | Push Generators are an alternative to the Async Generator proposal currently in the strawman stage. They are proposed for ES2016. 4 | 5 | ## The Problem 6 | 7 | JavaScript programs are single-threaded and therefore must steadfastly avoid blocking on IO operations. Today web developers must deal with a steadily-increasing number of push stream APIs including... 8 | 9 | * Server sent events 10 | * Web sockets 11 | * DOM events 12 | 13 | Unfortunately consuming these data sources in JavaScript is inconvenient. Asynchronous functions (proposed for ES7) provide language support for functions that push a single result, allowing loops and try/catch to be used for control flow and error handling respectively. ES6 introduced language support for producing and consuming functions that return multiple results. However no language support is currently proposed for functions that push multiple values. The push generator proposal is intended to resolve this discrepancy, and add symmetrical support for push and pull functions in JavaScript. 14 | 15 | ## Iteration and Observation 16 | 17 | Iteration and Observation are two patterns which allow a consumer to progressively consume multiple values produced by a producer. In each of these patterns, the producer may produce one of three types of notifications: 18 | 19 | a) a value 20 | b) an error 21 | c) a final value 22 | 23 | When the producer notifies the consumer of an error or a final value, no further values will be produced. Furthermore in both patterns the producer or consumer should be able to short-circuit at any time and elect to produce or receive no further notifications. 24 | 25 | ### Iteration 26 | 27 | Iteration puts the consumer in control. In iteration the producer waits for the consumer to *pull* a value. The consumer may synchronously or asynchronously *pull* values from the producer (the iterator), and the producer must synchronously produce values. 28 | 29 | In ES6, language support was added for producing and consuming values via iteration. 30 | 31 | ```JavaScript 32 | // data producer 33 | function* nums() { 34 | yield 1; 35 | yield 2; 36 | return 3; 37 | } 38 | 39 | // data consumer 40 | function consume() { 41 | for(var x of nums()) { 42 | console.log(x); 43 | } 44 | } 45 | 46 | consume(); 47 | // prints 48 | // 1 49 | // 2 50 | ``` 51 | 52 | Generator functions are considered *pull* functions, because the producer delivers notifications in the return position of function. This is evident in the desugared version of the for...of statement: 53 | 54 | ```JavaScript 55 | function nums() { 56 | var state = 0; 57 | return { 58 | next(v) { 59 | switch(state) { 60 | case 0: 61 | state = 1; 62 | return {value: 1, done: false}; 63 | case 1: 64 | state = 2; 65 | return {value: 2, done: false}; 66 | case 2: 67 | state = 3; 68 | return {value: 3, done: true}; 69 | caase 3: 70 | 71 | } 72 | } 73 | 74 | function consume() { 75 | var generator = nums(), 76 | pair; 77 | 78 | // value received in return position of next() 79 | while(!(pair = generator.next()).done) { 80 | console.log(pair.value); 81 | } 82 | } 83 | 84 | consume(); 85 | // prints 86 | // 1 87 | // 2 88 | ``` 89 | 90 | The producer may end the stream by either throwing when next is called or returning an IterationResult with a done value of true. The consumer may short-circuit the iteration process by invoking the return() method on the data producer (iterator). 91 | ```JavaScript 92 | generator.return(); 93 | ``` 94 | Note that there is no way for the producer to asynchronously notify the consumer that the stream has completed. Instead the producer must wait until the consumer requests a new value to indicate stream completion. 95 | 96 | Iteration works well for consuming streams of values that can be produced synchronously, such as in-memory collections or lazily-computed values (ex. fibonnacci sequence). However it is not possible to push streams such as DOM events or Websockets as iterators, because they produce their values asynchronously. For these data sources is necessary to use observation. 97 | 98 | ### Observation 99 | 100 | Observation puts the producer in control. The producer may synchronously or asynchronouly *push* values to the consumer's sink (observer), but the consumer must handle each value synchronously. In observation the consumer waits for the producer to *push* a value. 101 | 102 | The push generator proposal would add syntactic support for producing and consuming push streams of data to ES2016: 103 | 104 | ```JavaScript 105 | // data producer 106 | function*> nums() { 107 | yield 1; 108 | yield 2; 109 | return 3; 110 | } 111 | 112 | // data consumer 113 | function*> consume() { 114 | for(var x on nums()) { 115 | console.log(x); 116 | } 117 | } 118 | 119 | // note that two functions calls are required, one to create the Observable and another to being observation 120 | consume()(); 121 | // prints 122 | // 1 123 | // 2 124 | ``` 125 | 126 | Push Generator functions are considered *push*, because the producer delivers notifications in the *argument* position of the function. This is evident in the desugared version of the code above: 127 | 128 | ```JavaScript 129 | function nums() { 130 | return { 131 | [Symbol.observer](generator = { next() {}, throw() {}, return() {} }) { 132 | var iterationResult = generator.next(1); 133 | if (iterationResult.done) break; 134 | iterationResult = generator.next(2); 135 | if (iterationResult.done) break; 136 | iterationResult = generator.return(3); 137 | 138 | return generator; 139 | } 140 | } 141 | }; 142 | 143 | function consume() { 144 | return { 145 | [Symbol.observer](generator = { next() {}, throw() {}, return() {} }) { 146 | return nums()[Symbol.observer]({ 147 | next(v) { console.log(v); } 148 | }); 149 | } 150 | }; 151 | }; 152 | 153 | // note that two functions calls are required, one to create the Observable and another to being observation 154 | consume()(); 155 | // prints 156 | // 1 157 | // 2 158 | ``` 159 | 160 | The push generator proposal would add symmetrical support for Iteration and Observation to JavaScript. Like generator functions, *push generator functions* allow functions to return multiple values. However push generator functions send values to consumers via *Observation* rather than Iteration. The for..._on_ loop is also introduced to enable values to be consumed via observation. The process of Observation is standardized using a new interface: Observable. 161 | 162 | ## Introducing Observable 163 | 164 | ES6 introduces the Generator interface, which is a combination of two different interfaces: 165 | 166 | 1. Iterator 167 | 2. Observer 168 | 169 | The Iterator is a data source that can return a value, an error (via throw), or a final value (value where IterationResult::done). 170 | 171 | ```JavaScript 172 | interface Iterator { 173 | IterationResult next(); 174 | } 175 | 176 | type IterationResult = {done: boolean, value: any} 177 | 178 | interface Iterable { 179 | Iterator iterator(); 180 | } 181 | ``` 182 | 183 | The Observer is a data _sink_ which can be pushed a value, an error (via throw()), or a final value (return()): 184 | 185 | ```JavaScript 186 | interface Observer { 187 | void next(value); 188 | void return(returnValue); 189 | void throw(error); 190 | } 191 | ``` 192 | 193 | These two data types mixed together forms a Generator: 194 | 195 | ```JavaScript 196 | interface Generator { 197 | IterationResult next(value); 198 | IterationResult return(returnValue); 199 | IterationResult throw(error); 200 | } 201 | ``` 202 | 203 | Iteration and Observation both enable a consumer to progressively retrieve 0...N values from a producer. _The only difference between Iteration and Observation is the party in control._ In iteration the consumer is in control because the consumer initiates the request for a value, and the producer must synchronously respond. 204 | 205 | In this example a consumer requests an Iterator from an Array, and progressively requests the next value until the stream is exhausted. 206 | 207 | ```JavaScript 208 | function printNums(arr) { 209 | // requesting an iterator from the Array, which is an Iterable 210 | var iterator = arr[Symbol.iterator](), 211 | pair; 212 | // consumer (this function) 213 | while(!(pair = iterator.next()).done) { 214 | console.log(pair.value); 215 | } 216 | } 217 | ``` 218 | 219 | This code relies on the fact that in ES6, all collections implement the Iterable interface. ES6 also added special support for...of syntax, the program above can be rewritten like this: 220 | 221 | ```JavaScript 222 | function printNums(arr) { 223 | for(var value of arr) { 224 | console.log(value); 225 | } 226 | } 227 | ``` 228 | 229 | ES6 added great support for Iteration, but currently there is no equivalent of the Iterable type for Observation. How would we design such a type? By taking the dual of the Iterable type. 230 | 231 | ```JavaScript 232 | interface Iterable { 233 | Generator [Symbol.iterator]() 234 | } 235 | ``` 236 | 237 | The dual of a type is derived by swapping the argument and return types of its methods, and taking the dual of each term. The dual of a Generator is a Generator, because it is symmetrical. The generator can both accept and return the same three types of notifications: 238 | 239 | 1. data 240 | 2. error 241 | 3. final value 242 | 243 | Therefore all that is left to do is swap the arguments and return type of the Iterator's iterator method and then we have an Observable. 244 | 245 | ```JavaScript 246 | interface Observable { 247 | void [Symbol.observer](Generator observer) 248 | } 249 | ``` 250 | 251 | This interface is too simple. If iteration and observation can be thought of as long running functions, the party that is not in control needs a way to short-circuit the operation. In the case of observation, the producer is in control. As a result the consumer needs a way of terminating observation. If we use the terminology of events, we would say the consumer needs a way to _unsubscribe_. To allow for this, we make the following modification to the Observable interface: 252 | 253 | ```JavaScript 254 | interface Observation { 255 | void unobserve(); 256 | } 257 | 258 | interface Observable { 259 | Observation [Symbol.observer](Generator observer) 260 | } 261 | ``` 262 | 263 | This version of the Observable interface accepts a Generator and returns an Observation. The consumer can short-circuit observation (unsubscribe) by invoking the return() method on the Generator object returned for the Observable @@observer method. To demonstrate how this works, let's take a look at how we can adapt a common push stream API (DOM event) to an Observable. 264 | 265 | ```JavaScript 266 | // The decorate method accepts a generator and dynamically inherits a new generator from it 267 | // using Object.create. The new generator wraps the next, return, and throw methods, 268 | // intercepts any terminating operations, and invokes an onDone callback. 269 | // This includes calls to return, throw, or next calls that return a pair with done === true 270 | function decorate(generator, onDone) { 271 | var decoratedGenerator = Object.create(generator); 272 | decoratedGenerator.next = function(v) { 273 | var pair = generator.next(v); 274 | // if generator returns done = true, invoke onDone callback 275 | if (pair && pair.done) { 276 | onDone(); 277 | } 278 | 279 | return pair; 280 | }; 281 | 282 | ["throw","return"].forEach(method => { 283 | var superMethod = generator[method]; 284 | decoratedGenerator[method] = function(v) { 285 | // if either throw or return invoked, invoke onDone callback 286 | onDone(); 287 | superMethod.call(generator, v); 288 | }; 289 | }); 290 | } 291 | 292 | // Convert any DOM event into an push generator 293 | Observable.fromEvent = function(dom, eventName) { 294 | // An Observable is created by passing the defn of its observer method to its constructor 295 | return new Observable(function observer(generator) { 296 | var handler, 297 | decoratedGenerator = 298 | decorate( 299 | generator, 300 | // callback to invoke if generator is terminated 301 | function onDone() { 302 | dom.removeEventListener(eventName, handler); 303 | }); 304 | handler = function(e) { 305 | decoratedGenerator.next(e); 306 | }; 307 | 308 | dom.addEventListener(eventName, handler); 309 | 310 | return decoratedGenerator; 311 | }); 312 | }; 313 | 314 | // Adapt a DOM element's mousemoves to an Observable 315 | var mouseMoves = Observable.fromEvent(document.createElement('div'), "mousemove"); 316 | 317 | // subscribe to Observable stream of mouse moves 318 | var decoratedGenerator = mouseMoves[@@observer]({ 319 | next(e) { 320 | console.log(e); 321 | } 322 | }); 323 | 324 | // unsubscribe 2 seconds later 325 | setTimeout(function() { 326 | // short-circuit the observation/unsubscribe 327 | decoratedGenerator.return(); 328 | }, 2000); 329 | ``` 330 | 331 | Observable is the data type that a function modified by both * and async returns, because it _pushes_ multiple values. 332 | 333 | | | Sync | Async | 334 | | ------------- |:-------------:|:-------------:| 335 | | function | T | Promise | 336 | | function* | Iterator | Observable | 337 | 338 | An Observable accepts a generator and pushes it 0...N values and optionally terminates by either pushing an error or a return value. The consumer can also short-circuit by calling return() on the Generator object returned from the Observable's Symbol.observer method. 339 | 340 | In ES7, any collection that is Iterable should also be Observable. Here is an implementation for Array. 341 | 342 | ``` 343 | Array.prototype[Symbol.observer] = function(observer) { 344 | var done, 345 | decoratedObserver = decorate(observer, () => done = true); 346 | 347 | for(var x of this) { 348 | decoratedObserver.next(v); 349 | if (done) { 350 | return; 351 | } 352 | } 353 | decoratedObserver.return(); 354 | 355 | return decoratedObserver; 356 | }; 357 | ``` 358 | 359 | ## Adapting existing push APIs to Observable 360 | 361 | It's easy to adapt the web's many push stream APIs to Observable. 362 | 363 | ### Adapting DOM events to Observable 364 | ```JavaScript 365 | Observable.fromEvent = function(dom, eventName) { 366 | // An Observable is created by passing the defn of its observer method to its constructor 367 | return new Observable(function observer(generator) { 368 | var handler, 369 | decoratedGenerator = 370 | decorate( 371 | generator, 372 | // callback to invoke if generator is terminated 373 | function onDone() { 374 | dom.removeEventListener(eventName, handler); 375 | }); 376 | handler = function(e) { 377 | decoratedGenerator.next(e); 378 | }; 379 | 380 | dom.addEventListener(eventName, handler); 381 | 382 | return decoratedGenerator; 383 | }); 384 | }; 385 | ``` 386 | 387 | ### Adapting Object.observe to Observable 388 | 389 | ```JavaScript 390 | Observable.fromEventPattern = function(add, remove) { 391 | // An Observable is created by passing the defn of its observer method to its constructor 392 | return new Observable(function observer(generator) { 393 | var handler, 394 | decoratedGenerator = 395 | decorate( 396 | generator, 397 | function() { 398 | remove(handler); 399 | }); 400 | 401 | handler = decoratedGenerator.next.bind(decoratedGenerator); 402 | 403 | add(handler); 404 | 405 | return decoratedGenerator; 406 | }); 407 | }; 408 | 409 | Object.observations = function(obj) { 410 | return Observable.fromEventPattern( 411 | Object.observe.bind(Object, obj), 412 | Object.unobserve.bind(Object, obj)); 413 | }; 414 | ``` 415 | 416 | ### Adapting WebSocket to Observable 417 | 418 | ```JavaScript 419 | 420 | Observable.fromWebSocket = function(ws) { 421 | // An Observable is created by passing the defn of its observer method to its constructor 422 | return new Observable(function observer(generator) { 423 | var done = false, 424 | decoratedGenerator = 425 | decorate( 426 | generator, 427 | () => { 428 | if (!done) { 429 | done = true; 430 | ws.close(); 431 | ws.onmessage = null; 432 | ws.onclose = null; 433 | ws.onerror = null; 434 | } 435 | }); 436 | 437 | ws.onmessage = function(m) { 438 | decoratedGenerator.next(m); 439 | }; 440 | 441 | ws.onclose = function() { 442 | done = true; 443 | decoratedGenerator.return(); 444 | }; 445 | 446 | ws.onerror = function(e) { 447 | done = true; 448 | decoratedGenerator.throw(e); 449 | }; 450 | 451 | return decoratedGenerator; 452 | }); 453 | } 454 | ``` 455 | 456 | ### Adapting setInterval to Observable 457 | 458 | ```JavaScript 459 | Observable.interval = function(time) { 460 | return new Observable(function observer(generator) { 461 | var handle, 462 | decoratedGenerator = decorate(generator, function() { clearInterval(handle); }); 463 | 464 | handle = setInterval(function() { 465 | decoratedGenerator.next(); 466 | }, time); 467 | 468 | return decoratedGenerator; 469 | }); 470 | }; 471 | ``` 472 | ## Observable Composition 473 | 474 | The Observable type is composable. Once the various push stream APIs have been adapted to the Observable interface, it becomes possible to build complex asynchronous applications via composition instead of state machines. Third party libraries (a la Underscore) can easily be written which allow developers to build complex asynchronous applications using a declarative API similar to that of JavaScript's Array. Examples of such methods defined for Observable are included in this repo, but are _not_ proposed for standardization. 475 | 476 | Let's take the following three Array methods: 477 | ```JavaScript 478 | [1,2,3].map(x => x + 1) // [2,3,4] 479 | [1,2,3].filter(x => x > 1) // [2,3] 480 | ``` 481 | Now let's also imagine that Array had the following method: 482 | ```JavaScript 483 | [1,2,3].concatMap(x => [x + 1, x + 2]) // [2,3,3,4,4,5] 484 | ``` 485 | The concatMap method is a slight variation on map. The function passed to concatMap _must_ return an Array for each value it receives. This creates a tree. Then concatMap concatenates each inner array together left-to-right and flattens the tree by one dimension. 486 | ```JavaScript 487 | [1,2,3].map(x => [x + 1, x + 2]) // [[2,3],[3,4],[4,5]] 488 | [1,2,3].concatMap(x => [x + 1, x + 2]) // [2,3,3,4,4,5] 489 | ``` 490 | Note: Some may know concatMap by the name "flatMap", but I use the name concatMap deliberately and the reasons will soon become obvious. 491 | 492 | These three methods are surprisingly versatile. Here's an example of some code that retrieves your favorite Netflix titles. 493 | 494 | ```JavaScript 495 | var user = { 496 | genreLists: [ 497 | { 498 | name: "Drama", 499 | titles: [ 500 | { id: 66, name: "House of Cards", rating: 5 }, 501 | { id: 22, name: "Orange is the New Black", rating: 5 }, 502 | // more titles snipped 503 | ] 504 | }, 505 | { 506 | name: "Comedy", 507 | titles: [ 508 | { id: 55, name: "Arrested Development", rating: 5 }, 509 | { id: 22, name: "Orange is the New Black", rating: 5 }, 510 | // more titles snipped 511 | ] 512 | }, 513 | // more genre lists snipped 514 | ] 515 | } 516 | 517 | // for each genreList, the map fn returns an array of all titles with 518 | // a rating of 5.0. These title arrays are then concatenated together 519 | // to create a flat list of the user's favorite titles. 520 | function getFavTitles(user) { 521 | return user.genreLists.concatMap(genreList => 522 | genreList.titles.filter(title => title.rating === 5)); 523 | } 524 | 525 | // we consume the titles and write the to the console 526 | getFavTitles(user).forEach(title => console.log(title.rating)); 527 | 528 | ``` 529 | Using nearly the same code, we can build a drag and drop event. Observables are streams of values that arrive over time. They can be composed using the same Array methods we used in the example above (and a few more). 530 | In this example we compose Observables together to create a mouse drag event for a DOM element. 531 | 532 | ```JavaScript 533 | // for each mouse down event, the map fn returns the stream 534 | // of all the mouse move events that will occur until the 535 | // next mouse up event. This creates a stream of streams, 536 | // each of which is concatenated together to form a flat 537 | // stream of all the mouse drag events there ever will be. 538 | function getMouseDrags(elmt) { 539 | var mouseDowns = Observable.fromEvent(elmt, "mousedown"), 540 | var documentMouseMoves = Observable.fromEvent(document.body, "mousemove"), 541 | var documentMouseUps = Observable.fromEvent(document.body, "mouseup"); 542 | 543 | return mouseDowns.concatMap(mouseDown => 544 | documentMouseMoves.takeUntil(documentMouseUps)); 545 | }; 546 | 547 | var image = document.createElement("img"); 548 | document.body.appendChild(image); 549 | 550 | getMouseDrags(image).forEach(dragEvent => { 551 | image.style.left = dragEvent.clientX; 552 | image.style.top = dragEvent.clientY; 553 | }); 554 | ``` 555 | 556 | ### Asynchronous Observation 557 | 558 | Observation puts control in the hands of the producer. The producer may asynchronously send values, but the consumer must handle those values synchronously to avoid receiving interleaving next() calls. In some situations the consumer is unable to handle a value synchronously and must prevent the producer from sending more values until it has asynchronously handled a value. This pattern is known as _asynchronous observation_. 559 | 560 | One example of asynchronous observation is asynchronous I/O, in which both the sink and source are asynchronous. The read stream must wait until the write stream has asynchronously handled a value. This is sometimes referred to as_back pressure_. 561 | 562 | Asynchronous observation arises from the composition of for...on and await. Here's an example: 563 | 564 | ```JavaScript 565 | async function testFn() { 566 | var writer = new AsyncStreamWriter("/..."); 567 | 568 | for(var x on new AsyncStreamReader("/...")) { 569 | await writer.write(x); 570 | } 571 | } 572 | ``` 573 | 574 | Note that in the example above, the promise created by the write operation is being awaited within the body of the for…on loop. To avoid concurrent write operations, the Data source must wait until the data sink has asynchronously finished handling each value. How is this accomplished? 575 | 576 | ###The Asynchronous Observable 577 | 578 | An asynchronous observable waits until a consumer has finished handling a value before sending more values. This is accomplished by waiting on promises returned from the generator's next(), throw(), and return() methods. Here’s an example of an asynchronous generator function that returns an asynchronous observable. 579 | 580 | ```JavaScript 581 | function^^ getStocks() { 582 | var reader = new AsyncFileReader(“stocks.txt”); 583 | try { 584 | while(!reader.eof) { 585 | var line = await reader.readLine(); 586 | // If the yield expression is replaced by a promise, 587 | // the loop is paused until the promise is fullfilled. 588 | await yield JSON.parse(line); 589 | } 590 | } 591 | finally { 592 | await reader.close(); 593 | } 594 | } 595 | ``` 596 | 597 | Note that the asynchronous observable returned by the function above awaits promises returned by the generator before sending more values. We can desugar the code above to this: 598 | 599 | ```JavaScript 600 | function spawn(genF) { 601 | return new Promise(function(resolve, reject) { 602 | var gen = genF(); 603 | function step(nextF) { 604 | var next; 605 | try { 606 | next = nextF(); 607 | } catch(e) { 608 | // finished with failure, reject the promise 609 | reject(e); 610 | return; 611 | } 612 | if(next.done) { 613 | // finished with success, resolve the promise 614 | resolve(next.value); 615 | return; 616 | } 617 | else if (next.value && next.value.then) { 618 | // not finished, chain off the yielded promise and `step` again 619 | Promise.cast(next.value).then(function(v) { 620 | step(function() { return gen.next(v); }); 621 | }, function(e) { 622 | step(function() { return gen.throw(e); }); 623 | }); 624 | } 625 | else { 626 | // ES6 tail recursion prevents stack growth 627 | step(function() { return gen.next(next.value)}); 628 | } 629 | } 630 | step(function() { return gen.next(undefined); }); 631 | }); 632 | } 633 | 634 | function() { 635 | return new Observable(function observer(generator) { 636 | var done, 637 | decoratedGenerator = Object.create(generator); 638 | 639 | ["return", "throw"].forEach(method => { 640 | decoratedGenerator[method] = function (arg) { 641 | done = true; 642 | generator[method].call(this, arg); 643 | }; 644 | }); 645 | 646 | decoratedGenerator.next = function(v) { 647 | var pair = generator.next.call(this, v); 648 | done = pair.done; 649 | return pair; 650 | }; 651 | 652 | spawn(function*() { 653 | var reader, 654 | line, 655 | pair; 656 | 657 | // generator.return() could've been invoked before this function ran 658 | if (done) { return; } 659 | 660 | reader = new AsyncFileReader("stocks.txt"), 661 | try { 662 | while(!reader.eof) { 663 | // send promise to spawn fn to be resolved 664 | line = yield reader.readLine(); 665 | // generator.return() could've been invoked while this promise was resolving 666 | if (done) { return; } 667 | // Send value to generator 668 | pair = decoratedGenerator.next(JSON.parse(line)); 669 | if (done) { 670 | return; 671 | } 672 | else { 673 | // send promise (or regular value) to spawn fn to be resolved 674 | yield pair.value; 675 | // generator.return() could've been invoked while this promise was resolving 676 | if (done) { return; } 677 | } 678 | } 679 | } 680 | finally { 681 | // send promise (or regular value) to spawn fn to be resolved 682 | yield reader.close(); 683 | // generator.return() could've been invoked while this promise was resolving 684 | if (done) { return; } 685 | } 686 | }).then( 687 | v => { 688 | if (!done) { 689 | decoratedGenerator.return(v); 690 | } 691 | }, 692 | e => { 693 | if (!done) { 694 | decoratedGenerator.throw(e); 695 | } 696 | }); 697 | 698 | return decoratedGenerator; 699 | }); 700 | } 701 | ``` 702 | 703 | ## A Note on Iterable and Observable Duality 704 | 705 | The fact that the Observable and Iterable interface are not strict duals is a smell. If Observation and Iteration are truly dual, the correct definition of Iterable should be this: 706 | 707 | ```JavaScript 708 | interface Iterable { 709 | Generator iterator(Generator); 710 | } 711 | ``` 712 | In fact this definition is more useful than the current ES6 definition. In iteration, the party not in control is the producer. Using the same decorator pattern, the producer can now short-circuit the iterator without waiting for the consumer to call next. All the producer must do is invoke return() on the Generator passed to it, and the consumer will be notified. Now we have achieved duality, and given the party that is not in control the ability to short-circuit. I contend that collections should implement this new Iterable contract in ES7. 713 | 714 | # Transpilation 715 | 716 | Async generators can be transpiled into Async functions. A transpiler is in the works. Here's an example of the expected output. 717 | 718 | The following code... 719 | 720 | ```JavaScript 721 | function^^ getStockPrices(stockNames, nameServiceUrl) { 722 | var stockPriceServiceUrl = await getStockPriceServiceUrl(); 723 | 724 | // observable.buffer() -> AsyncObservable that supports backpressure by buffering 725 | for(var name on stockNames.buffer()) { 726 | // accessing arguments array instead of named paramater to demonstrate necessary renaming 727 | var price = await getPrice(await getSymbol(name, arguments[1]), stockPriceServiceUrl), 728 | topStories = []; 729 | 730 | for(var topStory on getStories(symbol).take(10)) { 731 | topStories.push(topStory); 732 | 733 | if (topStory.length === 2000) { 734 | break; 735 | } 736 | } 737 | 738 | // grab the last value in getStories - technically it's actually the return value, not the last next() value. 739 | var firstEverStory = await* getStories(); 740 | 741 | // grab all similar stock prices and return them in the stream immediately 742 | // short-hand for: for(var x on obs) { yield x } 743 | yield* getStockPrices(getSimilarStocks(symbol), nameServiceUrl); 744 | 745 | // This is just here to demonstrate that you shouldn't replace yields inside a function 746 | // scope that was present in the unexpanded source. Note that this is a regular 747 | // generator function, not an async one. 748 | var noop = function*() { 749 | yield somePromise; 750 | }; 751 | 752 | yield {name: name, price: price, topStories: topStories, firstEverStory: firstEverStory }; 753 | } 754 | } 755 | ``` 756 | 757 | ...can be transpiled into the [async/await](https://github.com/lukehoban/ecmascript-asyncawait) feature proposed for ES7: 758 | 759 | ```JavaScript 760 | function DecoratedGenerator(generator) { 761 | this.generator = generator; 762 | this.done = false; 763 | } 764 | 765 | DecoratedGenerator.prototype = {}; 766 | ["next","throw","return"].forEach(function(method) { 767 | DecoratedGenerator.prototype[method] = function(v) { 768 | if (this.done) break; 769 | var iterationResult = this.generator[method](v); 770 | this.done = iterationResult.done; 771 | return iterationResult; 772 | }; 773 | }); 774 | 775 | function Observable(observer) { 776 | this.observer = observer; 777 | } 778 | 779 | Observable.of = function(v) { 780 | return new Observable(function(observer) { 781 | var iterResult = observer.next(v); 782 | if (iterResult.done) break; 783 | observer.return(); 784 | return { dispose: function() }; 785 | }) 786 | } 787 | 788 | Observable.fromGenerator = function(genFn) { 789 | return new Observable(function(generator) { 790 | var subscription, 791 | iter = genFn(), 792 | process = function(v) { 793 | var result = iter.next(v); 794 | if (result instanceof Observable) { 795 | subscription = result.observer({ 796 | next: function(v) { 797 | }, 798 | throw: function(e) { 799 | return observer.throw(e); 800 | }, 801 | return: function(v) { 802 | process(v); 803 | } 804 | }); 805 | } 806 | else { 807 | return 808 | } 809 | }; 810 | generator = new DecoratedGenerator(generator); 811 | process(); 812 | return { 813 | dispose: function() { 814 | iter.return(); 815 | if (subscription) { 816 | subscription.dispose(); 817 | } 818 | } 819 | }; 820 | }) 821 | } 822 | ``` 823 | 824 | --------------------------------------------------------------------------------