| OLD | NEW | 
|---|
| 1 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a | 
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 /** | 5 /// This library adapts ES6 generators to implement Dart's async/await. | 
| 6  * This library adapts ES6 generators to implement Dart's async/await. | 6 /// It's designed to interact with Dart's Future/Stream and follow Dart | 
| 7  * | 7 /// async/await semantics. | 
| 8  * It's designed to interact with Dart's Future/Stream and follow Dart | 8 /// See https://github.com/dart-lang/dev_compiler/issues/245 for ideas on | 
| 9  * async/await semantics. | 9 /// reconciling Dart's Future and ES6 Promise. | 
| 10  * | 10 /// Inspired by `co`: https://github.com/tj/co/blob/master/index.js, which is a | 
| 11  * See https://github.com/dart-lang/dev_compiler/issues/245 for ideas on | 11 /// stepping stone for proposed ES7 async/await, and uses ES6 Promises. | 
| 12  * reconciling Dart's Future and ES6 Promise. | 12 library dart._generators; | 
| 13  * |  | 
| 14  * Inspired by `co`: https://github.com/tj/co/blob/master/index.js, which is a |  | 
| 15  * stepping stone for proposed ES7 async/await, and uses ES6 Promises. |  | 
| 16  */ |  | 
| 17 dart_library.library('dart/_generators', null, /* Imports */[ |  | 
| 18 ], /* Lazy Imports */[ |  | 
| 19   'dart/_operations', |  | 
| 20   'dart/_js_helper', |  | 
| 21   'dart/core', |  | 
| 22   'dart/collection', |  | 
| 23   'dart/async' |  | 
| 24 ], function(exports, _operations, _js_helper, core, collection, async) { |  | 
| 25   'use strict'; |  | 
| 26 | 13 | 
| 27   const _jsIterator = Symbol('_jsIterator'); | 14 import 'dart:_foreign_helper' show JS, JsName, rest, spread, genericTypeConstruc
     tor; | 
| 28   const _current = Symbol('_current'); | 15 import 'dart:_js_helper' show SyncIterable; | 
|  | 16 import 'dart:_operations' show instanceOf, stackTrace; | 
| 29 | 17 | 
| 30   function syncStar(gen, E, ...args) { | 18 import 'dart:async'; | 
| 31     const SyncIterable_E = _js_helper.SyncIterable$(E); | 19 | 
| 32     return new SyncIterable_E(gen, args); | 20 final _jsIterator = JS('', 'Symbol("_jsIterator")'); | 
|  | 21 final _current = JS('', 'Symbol("_current")'); | 
|  | 22 | 
|  | 23 syncStar(gen, E, @rest args) => JS('', '''(() => { | 
|  | 24   // TODO(ochafik). | 
|  | 25   const SyncIterable_E = ${genericTypeConstructor(SyncIterable)}($E); | 
|  | 26   return new SyncIterable_E($gen, $args); | 
|  | 27 })()'''); | 
|  | 28 | 
|  | 29 @JsName('async') | 
|  | 30 async_(gen, T, @rest args) => JS('', '''(() => { | 
|  | 31   let iter; | 
|  | 32   function onValue(res) { | 
|  | 33     if (res === void 0) res = null; | 
|  | 34     return next(iter.next(res)); | 
| 33   } | 35   } | 
| 34   exports.syncStar = syncStar; | 36   function onError(err) { | 
|  | 37     // If the awaited Future throws, we want to convert this to an exception | 
|  | 38     // thrown from the `yield` point, as if it was thrown there. | 
|  | 39     // | 
|  | 40     // If the exception is not caught inside `gen`, it will emerge here, which | 
|  | 41     // will send it to anyone listening on this async function's Future<T>. | 
|  | 42     // | 
|  | 43     // In essence, we are giving the code inside the generator a chance to | 
|  | 44     // use try-catch-finally. | 
|  | 45     return next(iter.throw(err)); | 
|  | 46   } | 
|  | 47   function next(ret) { | 
|  | 48     if (ret.done) return ret.value; | 
|  | 49     // Checks if the awaited value is a Future. | 
|  | 50     let future = ret.value; | 
|  | 51     if (!$instanceOf(future, ${genericTypeConstructor(Future)})) { | 
|  | 52       future = $Future.value(future); | 
|  | 53     } | 
|  | 54     // Chain the Future so `await` receives the Future's value. | 
|  | 55     return future.then(onValue, {onError: onError}); | 
|  | 56   } | 
|  | 57   return ${genericTypeConstructor(Future)}(T).new(function() { | 
|  | 58     iter = $gen(...$args)[Symbol.iterator](); | 
|  | 59     return onValue(); | 
|  | 60   }); | 
|  | 61 })()'''); | 
| 35 | 62 | 
| 36   function async_(gen, T, ...args) { | 63 // Implementation inspired by _AsyncStarStreamController in | 
| 37     let iter; | 64 // dart-lang/sdk's runtime/lib/core_patch.dart | 
| 38     function onValue(res) { | 65 // | 
| 39       if (res === void 0) res = null; | 66 // Given input like: | 
| 40       return next(iter.next(res)); | 67 // | 
| 41     } | 68 //     foo() async* { | 
| 42     function onError(err) { | 69 //       yield 1; | 
| 43       // If the awaited Future throws, we want to convert this to an exception | 70 //       yield* bar(); | 
| 44       // thrown from the `yield` point, as if it was thrown there. | 71 //       print(await baz()); | 
| 45       // | 72 //     } | 
| 46       // If the exception is not caught inside `gen`, it will emerge here, which | 73 // | 
| 47       // will send it to anyone listening on this async function's Future<T>. | 74 // This generates as: | 
| 48       // | 75 // | 
| 49       // In essence, we are giving the code inside the generator a chance to | 76 //    function foo() { | 
| 50       // use try-catch-finally. | 77 //      return dart.asyncStar(function*(stream) { | 
| 51       return next(iter.throw(err)); | 78 //        if (stream.add(1)) return; | 
| 52     } | 79 //        yield; | 
| 53     function next(ret) { | 80 //        if (stream.addStream(bar()) return; | 
| 54       if (ret.done) return ret.value; | 81 //        yield; | 
| 55       // Checks if the awaited value is a Future. | 82 //        print(yield baz()); | 
| 56       let future = ret.value; | 83 //      }); | 
| 57       if (!_operations.instanceOf(future, async.Future$)) { | 84 //    } | 
| 58         future = async.Future.value(future); | 85 final _AsyncStarStreamController = JS('', ''' | 
| 59       } |  | 
| 60       // Chain the Future so `await` receives the Future's value. |  | 
| 61       return future.then(onValue, {onError: onError}); |  | 
| 62     } |  | 
| 63     return async.Future$(T).new(function() { |  | 
| 64       iter = gen(...args)[Symbol.iterator](); |  | 
| 65       return onValue(); |  | 
| 66     }); |  | 
| 67   } |  | 
| 68   exports.async = async_; |  | 
| 69 |  | 
| 70   // Implementation inspired by _AsyncStarStreamController in |  | 
| 71   // dart-lang/sdk's runtime/lib/core_patch.dart |  | 
| 72   // |  | 
| 73   // Given input like: |  | 
| 74   // |  | 
| 75   //     foo() async* { |  | 
| 76   //       yield 1; |  | 
| 77   //       yield* bar(); |  | 
| 78   //       print(await baz()); |  | 
| 79   //     } |  | 
| 80   // |  | 
| 81   // This generates as: |  | 
| 82   // |  | 
| 83   //    function foo() { |  | 
| 84   //      return dart.asyncStar(function*(stream) { |  | 
| 85   //        if (stream.add(1)) return; |  | 
| 86   //        yield; |  | 
| 87   //        if (stream.addStream(bar()) return; |  | 
| 88   //        yield; |  | 
| 89   //        print(yield baz()); |  | 
| 90   //      }); |  | 
| 91   //    } |  | 
| 92   class _AsyncStarStreamController { | 86   class _AsyncStarStreamController { | 
| 93     constructor(generator, T, args) { | 87     constructor(generator, T, args) { | 
| 94       this.isAdding = false; | 88       this.isAdding = false; | 
| 95       this.isWaiting = false; | 89       this.isWaiting = false; | 
| 96       this.isScheduled = false; | 90       this.isScheduled = false; | 
| 97       this.isSuspendedAtYield = false; | 91       this.isSuspendedAtYield = false; | 
| 98       this.canceler = null; | 92       this.canceler = null; | 
| 99       this.iterator = generator(this, ...args)[Symbol.iterator](); | 93       this.iterator = generator(this, ...args)[Symbol.iterator](); | 
| 100       this.controller = async.StreamController$(T).new({ | 94       this.controller = ${genericTypeConstructor(StreamController)}(T).new({ | 
| 101         onListen: () => this.scheduleGenerator(), | 95         onListen: () => this.scheduleGenerator(), | 
| 102         onResume: () => this.onResume(), | 96         onResume: () => this.onResume(), | 
| 103         onCancel: () => this.onCancel() | 97         onCancel: () => this.onCancel() | 
| 104       }); | 98       }); | 
| 105     } | 99     } | 
| 106 | 100 | 
| 107     onResume() { | 101     onResume() { | 
| 108       if (this.isSuspendedAtYield) { | 102       if (this.isSuspendedAtYield) { | 
| 109         this.scheduleGenerator(); | 103         this.scheduleGenerator(); | 
| 110       } | 104       } | 
| 111     } | 105     } | 
| 112 | 106 | 
| 113     onCancel() { | 107     onCancel() { | 
| 114       if (this.controller.isClosed) { | 108       if (this.controller.isClosed) { | 
| 115         return null; | 109         return null; | 
| 116       } | 110       } | 
| 117       if (this.canceler == null) { | 111       if (this.canceler == null) { | 
| 118         this.canceler = async.Completer.new(); | 112         this.canceler = $Completer.new(); | 
| 119         this.scheduleGenerator(); | 113         this.scheduleGenerator(); | 
| 120       } | 114       } | 
| 121       return this.canceler.future; | 115       return this.canceler.future; | 
| 122     } | 116     } | 
| 123 | 117 | 
| 124     close() { | 118     close() { | 
| 125       if (this.canceler != null && !this.canceler.isCompleted) { | 119       if (this.canceler != null && !this.canceler.isCompleted) { | 
| 126         // If the stream has been cancelled, complete the cancellation future | 120         // If the stream has been cancelled, complete the cancellation future | 
| 127         // with the error. | 121         // with the error. | 
| 128         this.canceler.complete(); | 122         this.canceler.complete(); | 
| 129       } | 123       } | 
| 130       this.controller.close(); | 124       this.controller.close(); | 
| 131     } | 125     } | 
| 132 | 126 | 
| 133     scheduleGenerator() { | 127     scheduleGenerator() { | 
| 134       // TODO(jmesserly): is this paused check in the right place? Assuming the | 128       // TODO(jmesserly): is this paused check in the right place? Assuming the | 
| 135       // async* Stream yields, then is paused (by other code), the body will | 129       // async* Stream yields, then is paused (by other code), the body will | 
| 136       // already be scheduled. This will cause at least one more iteration to | 130       // already be scheduled. This will cause at least one more iteration to | 
| 137       // run (adding another data item to the Stream) before actually pausing. | 131       // run (adding another data item to the Stream) before actually pausing. | 
| 138       // It could be fixed by moving the `isPaused` check inside `runBody`. | 132       // It could be fixed by moving the `isPaused` check inside `runBody`. | 
| 139       if (this.isScheduled || this.controller.isPaused || | 133       if (this.isScheduled || this.controller.isPaused || | 
| 140           this.isAdding || this.isWaiting) { | 134           this.isAdding || this.isWaiting) { | 
| 141         return; | 135         return; | 
| 142       } | 136       } | 
| 143       this.isScheduled = true; | 137       this.isScheduled = true; | 
| 144       async.scheduleMicrotask(() => this.runBody()); | 138       $scheduleMicrotask(() => this.runBody()); | 
| 145     } | 139     } | 
| 146 | 140 | 
| 147     runBody(opt_awaitValue) { | 141     runBody(opt_awaitValue) { | 
| 148       this.isScheduled = false; | 142       this.isScheduled = false; | 
| 149       this.isSuspendedAtYield = false; | 143       this.isSuspendedAtYield = false; | 
| 150       this.isWaiting = false; | 144       this.isWaiting = false; | 
| 151       let iter; | 145       let iter; | 
| 152       try { | 146       try { | 
| 153         iter = this.iterator.next(opt_awaitValue); | 147         iter = this.iterator.next(opt_awaitValue); | 
| 154       } catch (e) { | 148       } catch (e) { | 
| 155         this.addError(e, _operations.stackTrace(e)); | 149         this.addError(e, $stackTrace(e)); | 
| 156         this.close(); | 150         this.close(); | 
| 157         return; | 151         return; | 
| 158       } | 152       } | 
| 159       if (iter.done) { | 153       if (iter.done) { | 
| 160         this.close(); | 154         this.close(); | 
| 161         return; | 155         return; | 
| 162       } | 156       } | 
| 163 | 157 | 
| 164       // If we're suspended at a yield/yield*, we're done for now. | 158       // If we're suspended at a yield/yield*, we're done for now. | 
| 165       if (this.isSuspendedAtYield || this.isAdding) return; | 159       if (this.isSuspendedAtYield || this.isAdding) return; | 
| 166 | 160 | 
| 167       // Handle `await`: if we get a value passed to `yield` it means we are | 161       // Handle `await`: if we get a value passed to `yield` it means we are | 
| 168       // waiting on this Future. Make sure to prevent scheduling, and pass the | 162       // waiting on this Future. Make sure to prevent scheduling, and pass the | 
| 169       // value back as the result of the `yield`. | 163       // value back as the result of the `yield`. | 
| 170       // | 164       // | 
| 171       // TODO(jmesserly): is the timing here correct? The assumption here is | 165       // TODO(jmesserly): is the timing here correct? The assumption here is | 
| 172       // that we should schedule `await` in `async*` the same as in `async`. | 166       // that we should schedule `await` in `async*` the same as in `async`. | 
| 173       this.isWaiting = true; | 167       this.isWaiting = true; | 
| 174       let future = iter.value; | 168       let future = iter.value; | 
| 175       if (!_operations.instanceOf(future, async.Future$)) { | 169       if (!$instanceOf(future, ${genericTypeConstructor(Future)})) { | 
| 176         future = async.Future.value(future); | 170         future = $Future.value(future); | 
| 177       } | 171       } | 
| 178       return future.then((x) => this.runBody(x), | 172       return future.then((x) => this.runBody(x), | 
| 179           { onError: (e, s) => this.throwError(e, s) }); | 173           { onError: (e, s) => this.throwError(e, s) }); | 
| 180     } | 174     } | 
| 181 | 175 | 
| 182     // Adds element to stream, returns true if the caller should terminate | 176     // Adds element to stream, returns true if the caller should terminate | 
| 183     // execution of the generator. | 177     // execution of the generator. | 
| 184     add(event) { | 178     add(event) { | 
| 185       // If stream is cancelled, tell caller to exit the async generator. | 179       // If stream is cancelled, tell caller to exit the async generator. | 
| 186       if (!this.controller.hasListener) return true; | 180       if (!this.controller.hasListener) return true; | 
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 218       if ((this.canceler != null) && !this.canceler.isCompleted) { | 212       if ((this.canceler != null) && !this.canceler.isCompleted) { | 
| 219         // If the stream has been cancelled, complete the cancellation future | 213         // If the stream has been cancelled, complete the cancellation future | 
| 220         // with the error. | 214         // with the error. | 
| 221         this.canceler.completeError(error, stackTrace); | 215         this.canceler.completeError(error, stackTrace); | 
| 222         return; | 216         return; | 
| 223       } | 217       } | 
| 224       if (!this.controller.hasListener) return; | 218       if (!this.controller.hasListener) return; | 
| 225       this.controller.addError(error, stackTrace); | 219       this.controller.addError(error, stackTrace); | 
| 226     } | 220     } | 
| 227   } | 221   } | 
|  | 222 '''); | 
| 228 | 223 | 
| 229   /** Returns a Stream of T implemented by an async* function. */ | 224 /// Returns a Stream of T implemented by an async* function. */ | 
| 230   function asyncStar(gen, T, ...args) { | 225 /// | 
| 231     return new _AsyncStarStreamController(gen, T, args).controller.stream; | 226 asyncStar(gen, T, @rest args) => JS('', '''(() => { | 
| 232   } | 227   return new _AsyncStarStreamController($gen, $T, $args).controller.stream; | 
| 233   exports.asyncStar = asyncStar; | 228 })()'''); | 
| 234 }); |  | 
| OLD | NEW | 
|---|