OLD | NEW |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * This library adapts ES6 generators to implement Dart's async/await. | |
7 * | |
8 * It's designed to interact with Dart's Future/Stream and follow Dart | |
9 * async/await semantics. | |
10 * | |
11 * See https://github.com/dart-lang/dev_compiler/issues/245 for ideas on | |
12 * reconciling Dart's Future and ES6 Promise. | |
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 */[ | 1 dart_library.library('dart/_generators', null, /* Imports */[ |
18 ], /* Lazy Imports */[ | 2 'dart/_classes' |
| 3 ], /* Lazy imports */[ |
| 4 'dart/_js_helper', |
19 'dart/_operations', | 5 'dart/_operations', |
20 'dart/_js_helper', | |
21 'dart/core', | |
22 'dart/collection', | |
23 'dart/async' | 6 'dart/async' |
24 ], function(exports, _operations, _js_helper, core, collection, async) { | 7 ], function(exports, classes, _js_helper, _operations, async$) { |
25 'use strict'; | 8 'use strict'; |
26 | 9 const _jsIterator = Symbol("_jsIterator"); |
27 const _jsIterator = Symbol('_jsIterator'); | 10 const _current = Symbol("_current"); |
28 const _current = Symbol('_current'); | |
29 | |
30 function syncStar(gen, E, ...args) { | 11 function syncStar(gen, E, ...args) { |
31 const SyncIterable_E = _js_helper.SyncIterable$(E); | 12 const SyncIterable_E = _js_helper.SyncIterable$(E); |
32 return new SyncIterable_E(gen, args); | 13 return new SyncIterable_E(gen, args); |
33 } | 14 } |
34 exports.syncStar = syncStar; | 15 function async(gen, T, ...args) { |
35 | |
36 function async_(gen, T, ...args) { | |
37 let iter; | 16 let iter; |
38 function onValue(res) { | 17 function onValue(res) { |
39 if (res === void 0) res = null; | 18 if (res === void 0) res = null; |
40 return next(iter.next(res)); | 19 return next(iter.next(res)); |
41 } | 20 } |
42 function onError(err) { | 21 function onError(err) { |
43 // If the awaited Future throws, we want to convert this to an exception | |
44 // thrown from the `yield` point, as if it was thrown there. | |
45 // | |
46 // If the exception is not caught inside `gen`, it will emerge here, which | |
47 // will send it to anyone listening on this async function's Future<T>. | |
48 // | |
49 // In essence, we are giving the code inside the generator a chance to | |
50 // use try-catch-finally. | |
51 return next(iter.throw(err)); | 22 return next(iter.throw(err)); |
52 } | 23 } |
53 function next(ret) { | 24 function next(ret) { |
54 if (ret.done) return ret.value; | 25 if (ret.done) return ret.value; |
55 // Checks if the awaited value is a Future. | |
56 let future = ret.value; | 26 let future = ret.value; |
57 if (!_operations.instanceOf(future, async.Future$)) { | 27 if (!_operations.instanceOf(future, async$.Future$)) { |
58 future = async.Future.value(future); | 28 future = async$.Future.value(future); |
59 } | 29 } |
60 // Chain the Future so `await` receives the Future's value. | |
61 return future.then(onValue, {onError: onError}); | 30 return future.then(onValue, {onError: onError}); |
62 } | 31 } |
63 return async.Future$(T).new(function() { | 32 return async$.Future$(T).new(function() { |
64 iter = gen(...args)[Symbol.iterator](); | 33 iter = gen(...args)[Symbol.iterator](); |
65 return onValue(); | 34 return onValue(); |
66 }); | 35 }); |
67 } | 36 } |
68 exports.async = async_; | 37 const _AsyncStarStreamController = class _AsyncStarStreamController { |
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 { | |
93 constructor(generator, T, args) { | 38 constructor(generator, T, args) { |
94 this.isAdding = false; | 39 this.isAdding = false; |
95 this.isWaiting = false; | 40 this.isWaiting = false; |
96 this.isScheduled = false; | 41 this.isScheduled = false; |
97 this.isSuspendedAtYield = false; | 42 this.isSuspendedAtYield = false; |
98 this.canceler = null; | 43 this.canceler = null; |
99 this.iterator = generator(this, ...args)[Symbol.iterator](); | 44 this.iterator = generator(this, ...args)[Symbol.iterator](); |
100 this.controller = async.StreamController$(T).new({ | 45 this.controller = async$.StreamController$(T).new({ |
101 onListen: () => this.scheduleGenerator(), | 46 onListen: (() => this.scheduleGenerator()).bind(this), |
102 onResume: () => this.onResume(), | 47 onResume: (() => this.onResume()).bind(this), |
103 onCancel: () => this.onCancel() | 48 onCancel: (() => this.onCancel()).bind(this) |
104 }); | 49 }); |
105 } | 50 } |
106 | |
107 onResume() { | 51 onResume() { |
108 if (this.isSuspendedAtYield) { | 52 if (this.isSuspendedAtYield) { |
109 this.scheduleGenerator(); | 53 this.scheduleGenerator(); |
110 } | 54 } |
111 } | 55 } |
112 | |
113 onCancel() { | 56 onCancel() { |
114 if (this.controller.isClosed) { | 57 if (this.controller.isClosed) { |
115 return null; | 58 return null; |
116 } | 59 } |
117 if (this.canceler == null) { | 60 if (this.canceler == null) { |
118 this.canceler = async.Completer.new(); | 61 this.canceler = async$.Completer.new(); |
119 this.scheduleGenerator(); | 62 this.scheduleGenerator(); |
120 } | 63 } |
121 return this.canceler.future; | 64 return this.canceler.future; |
122 } | 65 } |
123 | |
124 close() { | 66 close() { |
125 if (this.canceler != null && !this.canceler.isCompleted) { | 67 if (this.canceler != null && !this.canceler.isCompleted) { |
126 // If the stream has been cancelled, complete the cancellation future | |
127 // with the error. | |
128 this.canceler.complete(); | 68 this.canceler.complete(); |
129 } | 69 } |
130 this.controller.close(); | 70 this.controller.close(); |
131 } | 71 } |
132 | |
133 scheduleGenerator() { | 72 scheduleGenerator() { |
134 // TODO(jmesserly): is this paused check in the right place? Assuming the | 73 if (this.isScheduled || this.controller.isPaused || this.isAdding || this.
isWaiting) { |
135 // 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 | |
137 // run (adding another data item to the Stream) before actually pausing. | |
138 // It could be fixed by moving the `isPaused` check inside `runBody`. | |
139 if (this.isScheduled || this.controller.isPaused || | |
140 this.isAdding || this.isWaiting) { | |
141 return; | 74 return; |
142 } | 75 } |
143 this.isScheduled = true; | 76 this.isScheduled = true; |
144 async.scheduleMicrotask(() => this.runBody()); | 77 async$.scheduleMicrotask((() => this.runBody()).bind(this)); |
145 } | 78 } |
146 | |
147 runBody(opt_awaitValue) { | 79 runBody(opt_awaitValue) { |
148 this.isScheduled = false; | 80 this.isScheduled = false; |
149 this.isSuspendedAtYield = false; | 81 this.isSuspendedAtYield = false; |
150 this.isWaiting = false; | 82 this.isWaiting = false; |
151 let iter; | 83 let iter; |
152 try { | 84 try { |
153 iter = this.iterator.next(opt_awaitValue); | 85 iter = this.iterator.next(opt_awaitValue); |
154 } catch (e) { | 86 } catch (e) { |
155 this.addError(e, _operations.stackTrace(e)); | 87 this.addError(e, _operations.stackTrace(e)); |
156 this.close(); | 88 this.close(); |
157 return; | 89 return; |
158 } | 90 } |
| 91 |
159 if (iter.done) { | 92 if (iter.done) { |
160 this.close(); | 93 this.close(); |
161 return; | 94 return; |
162 } | 95 } |
163 | |
164 // If we're suspended at a yield/yield*, we're done for now. | |
165 if (this.isSuspendedAtYield || this.isAdding) return; | 96 if (this.isSuspendedAtYield || this.isAdding) return; |
166 | |
167 // 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 | |
169 // value back as the result of the `yield`. | |
170 // | |
171 // TODO(jmesserly): is the timing here correct? The assumption here is | |
172 // that we should schedule `await` in `async*` the same as in `async`. | |
173 this.isWaiting = true; | 97 this.isWaiting = true; |
174 let future = iter.value; | 98 let future = iter.value; |
175 if (!_operations.instanceOf(future, async.Future$)) { | 99 if (!_operations.instanceOf(future, async$.Future$)) { |
176 future = async.Future.value(future); | 100 future = async$.Future.value(future); |
177 } | 101 } |
178 return future.then((x) => this.runBody(x), | 102 return future.then((x => this.runBody(x)).bind(this), { |
179 { onError: (e, s) => this.throwError(e, s) }); | 103 onError: ((e, s) => this.throwError(e, s)).bind(this) |
| 104 }); |
180 } | 105 } |
181 | |
182 // Adds element to stream, returns true if the caller should terminate | |
183 // execution of the generator. | |
184 add(event) { | 106 add(event) { |
185 // If stream is cancelled, tell caller to exit the async generator. | |
186 if (!this.controller.hasListener) return true; | 107 if (!this.controller.hasListener) return true; |
187 this.controller.add(event); | 108 this.controller.add(event); |
188 this.scheduleGenerator(); | 109 this.scheduleGenerator(); |
189 this.isSuspendedAtYield = true; | 110 this.isSuspendedAtYield = true; |
190 return false; | 111 return false; |
191 } | 112 } |
192 | |
193 // Adds the elements of stream into this controller's stream. | |
194 // The generator will be scheduled again when all of the | |
195 // elements of the added stream have been consumed. | |
196 // Returns true if the caller should terminate | |
197 // execution of the generator. | |
198 addStream(stream) { | 113 addStream(stream) { |
199 // If stream is cancelled, tell caller to exit the async generator. | |
200 if (!this.controller.hasListener) return true; | 114 if (!this.controller.hasListener) return true; |
201 | |
202 this.isAdding = true; | 115 this.isAdding = true; |
203 this.controller.addStream(stream, {cancelOnError: false}).then(() => { | 116 this.controller.addStream(stream, {cancelOnError: false}).then((() => { |
204 this.isAdding = false; | 117 this.isAdding = false; |
205 this.scheduleGenerator(); | 118 this.scheduleGenerator(); |
206 }, { onError: (e, s) => this.throwError(e, s) }); | 119 }).bind(this), { |
| 120 onError: ((e, s) => this.throwError(e, s)).bind(this) |
| 121 }); |
207 } | 122 } |
208 | |
209 throwError(error, stackTrace) { | 123 throwError(error, stackTrace) { |
210 try { | 124 try { |
211 this.iterator.throw(error); | 125 this.iterator.throw(error); |
212 } catch (e) { | 126 } catch (e) { |
213 this.addError(e, stackTrace); | 127 this.addError(e, stackTrace); |
214 } | 128 } |
| 129 |
215 } | 130 } |
216 | |
217 addError(error, stackTrace) { | 131 addError(error, stackTrace) { |
218 if ((this.canceler != null) && !this.canceler.isCompleted) { | 132 if (this.canceler != null && !this.canceler.isCompleted) { |
219 // If the stream has been cancelled, complete the cancellation future | |
220 // with the error. | |
221 this.canceler.completeError(error, stackTrace); | 133 this.canceler.completeError(error, stackTrace); |
222 return; | 134 return; |
223 } | 135 } |
224 if (!this.controller.hasListener) return; | 136 if (!this.controller.hasListener) return; |
225 this.controller.addError(error, stackTrace); | 137 this.controller.addError(error, stackTrace); |
226 } | 138 } |
227 } | 139 }; |
228 | |
229 /** Returns a Stream of T implemented by an async* function. */ | |
230 function asyncStar(gen, T, ...args) { | 140 function asyncStar(gen, T, ...args) { |
231 return new _AsyncStarStreamController(gen, T, args).controller.stream; | 141 return new _AsyncStarStreamController(gen, T, args).controller.stream; |
232 } | 142 } |
| 143 // Exports: |
| 144 exports.syncStar = syncStar; |
| 145 exports.async = async; |
233 exports.asyncStar = asyncStar; | 146 exports.asyncStar = asyncStar; |
234 }); | 147 }); |
OLD | NEW |