Chromium Code Reviews| Index: sdk/lib/_internal/compiler/js_lib/js_helper.dart |
| diff --git a/sdk/lib/_internal/compiler/js_lib/js_helper.dart b/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
| index 7a75198df3ccaab7fb7ed24c9c56433c0fcc11ed..e9407bb846453897a7f89d2a3faaea21292ca41a 100644 |
| --- a/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
| +++ b/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
| @@ -25,7 +25,13 @@ import 'dart:_isolate_helper' show |
| enterJsAsync, |
| isWorker; |
| -import 'dart:async' show Future, DeferredLoadException, Completer; |
| +import 'dart:async' |
| + show Future, |
| + DeferredLoadException, |
| + Completer, |
| + StreamController, |
| + Stream, |
| + StreamSubscription; |
| import 'dart:_foreign_helper' show |
| DART_CLOSURE_TO_JS, |
| @@ -3462,3 +3468,262 @@ void badMain() { |
| void mainHasTooManyParameters() { |
| throw new MainError("'main' expects too many parameters."); |
| } |
| + |
| +/// Runtime support for async-await transformation. |
| +/// |
| +/// This function is called by a transformed function on each await and return |
| +/// in the untransformed function, and before starting. |
| +/// |
| +/// If [object] is not a future it will be wrapped in a `new Future.value`. |
| +/// |
| +/// If [helperCallback] is null it indicates a return from the async function, |
| +/// and we complete the completer with object. |
| +/// |
| +/// Otherwise [helperCallback] is set up to be called when the future is |
| +/// successfull and [errorCallback] if it is completed with an error. |
| +/// |
| +/// If helperCallback or errorCallback throws we complete the completer with the |
| +/// error. |
| +/// |
| +/// Returns the future of the completer for convenience of the first call. |
| +dynamic thenHelper(dynamic object, |
| + dynamic /* js function */ helperCallback, |
| + Completer completer, |
| + dynamic /* js function */ errorCallback) { |
| + if (helperCallback == null) { |
| + completer.complete(object); |
| + return; |
| + } |
| + Future future = object is Future ? object : new Future.value(object); |
| + future.then(_wrapJsFunction(helperCallback, completer), |
| + onError: _wrapJsFunction(errorCallback, completer)); |
|
floitsch
2015/02/04 12:31:29
this loses the stacktrace.
sigurdm
2015/02/05 14:06:06
True - this won't work. We have to find a good sol
|
| + return completer.future; |
| +} |
| + |
| +// Used to avoid creating two identical closure classes in [thenHelper]. |
|
floitsch
2015/02/04 12:31:29
Remove comment.
sigurdm
2015/02/05 14:06:05
Done.
|
| +Function _wrapJsFunction(dynamic /* js function */ function, |
|
floitsch
2015/02/04 12:31:29
name is too general.
sigurdm
2015/02/05 14:06:05
Made it more specific.
|
| + Completer completer) { |
| + return (result) { |
| + try { |
| + JS('', '#(#)', function, result); |
| + } catch (e, st) { |
| + completer.completeError(e, st); |
| + } |
| + }; |
| +} |
| + |
| + |
| +/// Runtime support for async*. |
| +/// |
| +/// Called by the transformed function for each original return, await, yield, |
|
floitsch
2015/02/04 12:31:29
Start by explaining what it does, not who uses it.
sigurdm
2015/02/05 14:06:05
Done.
|
| +/// yield* and before starting the function. |
| +/// |
| +/// On a return call with helperCallback == null, and we close the stream. |
|
floitsch
2015/02/04 12:31:29
?
sigurdm
2015/02/05 14:06:06
Reworded
|
| +/// |
| +/// If object is an [IterationMarker] it indicates a yield or yield*. |
|
floitsch
2015/02/04 12:31:29
what indicates?
floitsch
2015/02/04 12:31:29
reference [object]
sigurdm
2015/02/05 14:06:05
Done.
sigurdm
2015/02/05 14:06:05
Done.
sigurdm
2015/02/05 14:06:06
Tried to reword.
|
| +/// If the stream subscription has been canceled we schedule the errorCallback |
|
floitsch
2015/02/04 12:31:29
don't use "we" (here and in the rest of the docume
floitsch
2015/02/04 12:31:29
has been canceled when?
floitsch
2015/02/04 12:31:29
new line before? or "In that case..." ?
sigurdm
2015/02/05 14:06:05
Done.
sigurdm
2015/02/05 14:06:06
I will try to avoid it in the future. I use it to
|
| +/// that should run through enclosing finally blocks before exiting. |
|
floitsch
2015/02/04 12:31:29
that feels weird. A 'cancel' is not an error. Why
sigurdm
2015/02/05 14:06:05
errorCallback is used for running the finallies in
|
| +/// |
| +/// If it is a single-yield we add the value to the stream. |
|
floitsch
2015/02/04 12:31:29
If what is a single-yield? I assume it's the [obje
sigurdm
2015/02/05 14:06:05
Reworded
|
| +/// If the stream subscription has been paused we return early, otherwise |
| +/// we schedule the helper function to be executed again. |
| +/// |
| +/// If it is a yield* we start listening to the yielded stream, and add all |
| +/// events and errors to our own controller (taking care if the subscription has |
| +/// been paused or canceled) - when the sub-stream is done we schedule the |
| +/// helper function again. |
| +/// |
| +/// If object is not and [IterationMarker] it indicates an await. |
|
floitsch
2015/02/04 12:31:29
not what?
Reference [object].
(Whenever you would
sigurdm
2015/02/05 14:06:04
Acknowledged.
|
| +/// If object is not a Future, it is wrapped in a `Future.value`. |
| +/// Then set up helperCallBack to be called on successfull completion of the |
| +/// error |
| +/// |
| +/// If helperCallback or errorCallback throws we add the error to the stream |
| +/// If the object is a YIELD_SINGLE we add the object to the stream, and if the |
| +/// stream has been canceled call the errorCallBack. |
| +dynamic streamHelper(dynamic object, |
| + dynamic /* js function */ helperCallback, |
| + AsyncStarStreamController controller, |
| + dynamic /* js function */ errorCallback) { |
| + if (helperCallback == null) { |
| + // This happens on return from the async* function. |
| + controller.close(); |
| + return; |
| + } |
| + |
| + if (object is IterationMarker) { |
| + if (controller.stopRunning) { |
| + _wrapJsFunctionForStream(errorCallback, controller)(); |
| + return; |
| + } |
| + if (object.state == IterationMarker.YIELD_SINGLE) { |
| + controller.add(object.value); |
| + } else if (object.state == IterationMarker.YIELD_STAR) { |
| + Stream stream = object.value; |
|
floitsch
2015/02/04 12:31:29
can't you just controller.addStream(object.value).
sigurdm
2015/02/05 14:06:04
Brilliant - had not seen that!
|
| + controller.subSubscription = stream.listen((value) { |
| + if (controller.stopRunning) { |
| + controller.subSubscription.cancel(); |
| + _wrapJsFunctionForStream(errorCallback, controller)(null); |
| + return; |
| + } |
| + controller.add(value); |
| + }, onError: (error, stackTrace) { |
| + if (controller.stopRunning) { |
| + controller.subSubscription.cancel(); |
| + // This will run the enclosing finally blocks. |
| + _wrapJsFunctionForStream(errorCallback, controller)(null); |
| + return; |
| + } |
| + controller.addError(error, stackTrace); |
| + if (controller.isPaused) { |
| + controller.subSubscription.pause(); |
| + } |
| + }, onDone: () { |
| + controller.subSubscription = null; |
| + // Resume producing. |
| + streamHelper(null, helperCallback, controller, null); |
| + }); |
| + return; |
| + } |
| + if (controller.isPaused) { |
| + return; |
| + } |
| + object = null; |
| + } |
| + Future future = object is Future ? object : new Future.value(object); |
| + future.then(_wrapJsFunctionForStream(helperCallback, controller), |
| + onError: errorCallback = null |
| + ? null |
| + : _wrapJsFunctionForStream(errorCallback, controller)); |
| + return controller.stream; |
| +} |
| + |
| +/// A wrapper around a [StreamController] that remembers if that controller |
| +/// got a cancel. |
| +/// |
| +/// Also has a subSubscription that when not null will provide events for the |
| +/// stream, and will be paused and resumed along with this controller. |
| +class AsyncStarStreamController { |
| + StreamController controller; |
| + StreamSubscription subSubscription = null; |
| + Stream get stream => controller.stream; |
| + bool stopRunning = false; |
| + bool get isPaused => controller.isPaused; |
| + add(event) => controller.add(event); |
| + addError(error, stackTrace) => controller.addError(error, stackTrace); |
| + close() => controller.close(); |
| + |
| + AsyncStarStreamController(helperCallBack) { |
| + controller = new StreamController( |
| + onPause: () { |
| + if (subSubscription != null) { |
| + subSubscription.pause(); |
| + } |
| + }, |
| + onResume: () { |
| + if (subSubscription == null) { |
| + helperCallBack(null, helperCallBack, controller, null); |
| + } else { |
| + subSubscription.resume(); |
| + } |
| + }, onCancel: () { |
| + stopRunning = true; |
| + }); |
| + } |
| +} |
| + |
| +makeAsyncStarController(helperCallback) { |
| + return new AsyncStarStreamController(helperCallback); |
| +} |
| + |
| +// Used to avoid creating identical closure classes in [streamHelper]. |
|
floitsch
2015/02/04 12:31:29
Remove comment.
sigurdm
2015/02/05 14:06:04
Done.
|
| +Function _wrapJsFunctionForStream(dynamic /* js function */ function, |
| + AsyncStarStreamController controller) { |
| + return (result) { |
| + try { |
| + JS('', '#(#)', function, result); |
| + } catch (e, st) { |
|
floitsch
2015/02/04 12:31:29
I believe that errors in async* are fatal.
Maybe L
sigurdm
2015/02/05 14:06:05
Acknowledged.
We will have to find out.
|
| + controller.addError(e, st); |
| + } |
| + }; |
| +} |
| + |
| + |
| +class IterationMarker { |
| + static const YIELD_SINGLE = 0; |
| + static const YIELD_STAR = 1; |
| + static const ITERATION_ENDED = 2; |
| + |
| + final value; |
| + final int state; |
| + |
| + IterationMarker._(this.state, this.value); |
| + |
| + static yieldStar(Iterable iterable) { |
| + return new IterationMarker._(YIELD_STAR, iterable); |
| + } |
| + |
| + static endOfIteration() { |
| + return new IterationMarker._(ITERATION_ENDED, null); |
| + } |
| + |
| + static yieldSingle(value) { |
| + return new IterationMarker._(YIELD_SINGLE, value); |
| + } |
| + |
| + toString() => "IterationMarker($state, $value)"; |
| +} |
| + |
| +class SyncStarIterator implements Iterator { |
| + final Function _helper; |
| + |
| + // if [runningNested] this will be the nested iterator, otherwise it will be |
|
floitsch
2015/02/04 12:31:29
no need for future tense.
Start sentence with capi
sigurdm
2015/02/05 14:06:06
Done.
|
| + // the current value. |
| + dynamic _current = null; |
| + bool _runningNested = false; |
| + |
| + get current => _runningNested ? _current.current : _current; |
| + |
| + SyncStarIterator(helper) |
| + : _helper = ((arg) => JS('', '#(#)', helper, arg)); |
| + |
| + bool moveNext() { |
| + if (_runningNested) { |
| + if (_current.moveNext()) { |
| + return true; |
| + } else { |
| + _runningNested = false; |
| + } |
| + } |
| + _current = _helper(null); |
| + if (_current is IterationMarker) { |
| + if (_current.state == IterationMarker.ITERATION_ENDED) { |
| + _current = null; |
| + // We rely on [_helper] to repeatedly return `ITERATION_ENDED`. |
| + return false; |
| + } else { |
| + assert(_current.state == IterationMarker.YIELD_STAR); |
| + _current = _current.value.iterator; |
| + _runningNested = true; |
| + return moveNext(); |
| + } |
| + } |
| + return true; |
| + } |
| +} |
| + |
| +/// An Iterable corresponding to a sync* method. |
| +/// |
| +/// Each invocation of a sync* method will return a new instance of this class. |
| +class SyncStarIterable extends IterableBase { |
| + // This is a function that will return a helper function that does the |
| + // iteration of the sync*. |
| + // |
| + // Each invocation should give a helper with fresh state. |
| + final dynamic /* js function */ _outerHelper; |
| + |
| + SyncStarIterable(this._outerHelper); |
| + |
| + Iterator get iterator => new SyncStarIterator(JS('', '#()', _outerHelper)); |
| +} |
| + |