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 |