| Index: sdk/lib/_internal/js_runtime/lib/async_patch.dart
|
| diff --git a/sdk/lib/_internal/js_runtime/lib/async_patch.dart b/sdk/lib/_internal/js_runtime/lib/async_patch.dart
|
| index 137795734310ba4556bbcfec1581b1ebb23bf3de..f8412c1be549a41d274ab123fa242ab478cfbc9d 100644
|
| --- a/sdk/lib/_internal/js_runtime/lib/async_patch.dart
|
| +++ b/sdk/lib/_internal/js_runtime/lib/async_patch.dart
|
| @@ -6,9 +6,12 @@
|
|
|
| import 'dart:_js_helper' show
|
| patch,
|
| + ExceptionAndStackTrace,
|
| Primitives,
|
| convertDartClosureToJS,
|
| - requiresPreamble;
|
| + getTraceFromException,
|
| + requiresPreamble,
|
| + unwrapException;
|
| import 'dart:_isolate_helper' show
|
| IsolateNatives,
|
| TimerImpl,
|
| @@ -18,6 +21,8 @@ import 'dart:_isolate_helper' show
|
|
|
| import 'dart:_foreign_helper' show JS;
|
|
|
| +import 'dart:_async_await_error_codes' as async_error_codes;
|
| +
|
| @patch
|
| class _AsyncRun {
|
| @patch
|
| @@ -121,3 +126,383 @@ class Timer {
|
| return new TimerImpl.periodic(milliseconds, callback);
|
| }
|
| }
|
| +
|
| +/// 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 [asyncBody] is [async_error_codes.SUCCESS]/[async_error_codes.ERROR] it
|
| +/// indicates a return or throw from the async function, and
|
| +/// complete/completeError is called on [completer] with [object].
|
| +///
|
| +/// Otherwise [asyncBody] is set up to be called when the future is completed
|
| +/// with a code [async_error_codes.SUCCESS]/[async_error_codes.ERROR] depending
|
| +/// on the success of the future.
|
| +///
|
| +/// Returns the future of the completer for convenience of the first call.
|
| +dynamic _asyncHelper(dynamic object,
|
| + dynamic /* int | _WrappedAsyncBody */ bodyFunctionOrErrorCode,
|
| + Completer completer) {
|
| + if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) {
|
| + completer.complete(object);
|
| + return;
|
| + } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) {
|
| + // The error is a js-error.
|
| + completer.completeError(unwrapException(object),
|
| + getTraceFromException(object));
|
| + return;
|
| + }
|
| +
|
| + _awaitOnObject(object, bodyFunctionOrErrorCode);
|
| + return completer.future;
|
| +}
|
| +
|
| +/// Awaits on the given [object].
|
| +///
|
| +/// If the [object] is a Future, registers on it, otherwise wraps it into a
|
| +/// future first.
|
| +///
|
| +/// The [bodyFunction] argument is the continuation that should be invoked
|
| +/// when the future completes.
|
| +void _awaitOnObject(object, _WrappedAsyncBody bodyFunction) {
|
| + Function thenCallback =
|
| + (result) => bodyFunction(async_error_codes.SUCCESS, result);
|
| +
|
| + Function errorCallback = (dynamic error, StackTrace stackTrace) {
|
| + ExceptionAndStackTrace wrappedException =
|
| + new ExceptionAndStackTrace(error, stackTrace);
|
| + bodyFunction(async_error_codes.ERROR, wrappedException);
|
| + };
|
| +
|
| + if (object is _Future) {
|
| + // We can skip the zone registration, since the bodyFunction is already
|
| + // registered (see [_wrapJsFunctionForAsync]).
|
| + object._thenNoZoneRegistration(thenCallback, errorCallback);
|
| + } else if (object is Future) {
|
| + object.then(thenCallback, onError: errorCallback);
|
| + } else {
|
| + _Future future = new _Future();
|
| + future._setValue(object);
|
| + // We can skip the zone registration, since the bodyFunction is already
|
| + // registered (see [_wrapJsFunctionForAsync]).
|
| + future._thenNoZoneRegistration(thenCallback, null);
|
| + }
|
| +}
|
| +
|
| +typedef void _WrappedAsyncBody(int errorCode, dynamic result);
|
| +
|
| +_WrappedAsyncBody _wrapJsFunctionForAsync(dynamic /* js function */ function) {
|
| + var protected = JS('', """
|
| + // Invokes [function] with [errorCode] and [result].
|
| + //
|
| + // If (and as long as) the invocation throws, calls [function] again,
|
| + // with an error-code.
|
| + function(errorCode, result) {
|
| + while (true) {
|
| + try {
|
| + #(errorCode, result);
|
| + break;
|
| + } catch (error) {
|
| + result = error;
|
| + errorCode = #;
|
| + }
|
| + }
|
| + }""", function, async_error_codes.ERROR);
|
| + return Zone.current.registerBinaryCallback((int errorCode, dynamic result) {
|
| + JS('', '#(#, #)', protected, errorCode, result);
|
| + });
|
| +}
|
| +
|
| +/// Implements the runtime support for async* functions.
|
| +///
|
| +/// Called by the transformed function for each original return, await, yield,
|
| +/// yield* and before starting the function.
|
| +///
|
| +/// When the async* function wants to return it calls this function with
|
| +/// [asyncBody] == [async_error_codes.SUCCESS], the asyncStarHelper takes this
|
| +/// as signal to close the stream.
|
| +///
|
| +/// When the async* function wants to signal that an uncaught error was thrown,
|
| +/// it calls this function with [asyncBody] == [async_error_codes.ERROR],
|
| +/// the streamHelper takes this as signal to addError [object] to the
|
| +/// [controller] and close it.
|
| +///
|
| +/// If the async* function wants to do a yield or yield*, it calls this function
|
| +/// with [object] being an [IterationMarker].
|
| +///
|
| +/// In the case of a yield or yield*, if the stream subscription has been
|
| +/// canceled, schedules [asyncBody] to be called with
|
| +/// [async_error_codes.STREAM_WAS_CANCELED].
|
| +///
|
| +/// If [object] is a single-yield [IterationMarker], adds the value of the
|
| +/// [IterationMarker] to the stream. If the stream subscription has been
|
| +/// paused, return early. Otherwise schedule the helper function to be
|
| +/// executed again.
|
| +///
|
| +/// If [object] is a yield-star [IterationMarker], starts listening to the
|
| +/// yielded stream, and adds all events and errors to our own controller (taking
|
| +/// care if the subscription has been paused or canceled) - when the sub-stream
|
| +/// is done, schedules [asyncBody] again.
|
| +///
|
| +/// If the async* function wants to do an await it calls this function with
|
| +/// [object] not and [IterationMarker].
|
| +///
|
| +/// If [object] is not a [Future], it is wrapped in a `Future.value`.
|
| +/// The [asyncBody] is called on completion of the future (see [asyncHelper].
|
| +void _asyncStarHelper(dynamic object,
|
| + dynamic /* int | _WrappedAsyncBody */ bodyFunctionOrErrorCode,
|
| + _AsyncStarStreamController controller) {
|
| + if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) {
|
| + // This happens on return from the async* function.
|
| + if (controller.isCanceled) {
|
| + controller.cancelationCompleter.complete();
|
| + } else {
|
| + controller.close();
|
| + }
|
| + return;
|
| + } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) {
|
| + // The error is a js-error.
|
| + if (controller.isCanceled) {
|
| + controller.cancelationCompleter.completeError(
|
| + unwrapException(object),
|
| + getTraceFromException(object));
|
| + } else {
|
| + controller.addError(unwrapException(object),
|
| + getTraceFromException(object));
|
| + controller.close();
|
| + }
|
| + return;
|
| + }
|
| +
|
| + if (object is _IterationMarker) {
|
| + if (controller.isCanceled) {
|
| + bodyFunctionOrErrorCode(async_error_codes.STREAM_WAS_CANCELED, null);
|
| + return;
|
| + }
|
| + if (object.state == _IterationMarker.YIELD_SINGLE) {
|
| + controller.add(object.value);
|
| +
|
| + scheduleMicrotask(() {
|
| + if (controller.isPaused) {
|
| + // We only suspend the thread inside the microtask in order to allow
|
| + // listeners on the output stream to pause in response to the just
|
| + // output value, and have the stream immediately stop producing.
|
| + controller.isSuspended = true;
|
| + return;
|
| + }
|
| + bodyFunctionOrErrorCode(null, async_error_codes.SUCCESS);
|
| + });
|
| + return;
|
| + } else if (object.state == _IterationMarker.YIELD_STAR) {
|
| + Stream stream = object.value;
|
| + // Errors of [stream] are passed though to the main stream. (see
|
| + // [AsyncStreamController.addStream]).
|
| + // TODO(sigurdm): The spec is not very clear here. Clarify with Gilad.
|
| + controller.addStream(stream).then((_) {
|
| + // No check for isPaused here because the spec 17.16.2 only
|
| + // demands checks *before* each element in [stream] not after the last
|
| + // one. On the other hand we check for isCanceled, as that check happens
|
| + // after insertion of each element.
|
| + int errorCode = controller.isCanceled
|
| + ? async_error_codes.STREAM_WAS_CANCELED
|
| + : async_error_codes.SUCCESS;
|
| + bodyFunctionOrErrorCode(errorCode, null);
|
| + });
|
| + return;
|
| + }
|
| + }
|
| +
|
| + _awaitOnObject(object, bodyFunctionOrErrorCode);
|
| +}
|
| +
|
| +Stream _streamOfController(_AsyncStarStreamController controller) {
|
| + return controller.stream;
|
| +}
|
| +
|
| +/// A wrapper around a [StreamController] that keeps track of the state of
|
| +/// the execution of an async* function.
|
| +/// It can be in 1 of 3 states:
|
| +///
|
| +/// - running/scheduled
|
| +/// - suspended
|
| +/// - canceled
|
| +///
|
| +/// If yielding while the subscription is paused it will become suspended. And
|
| +/// only resume after the subscription is resumed or canceled.
|
| +class _AsyncStarStreamController {
|
| + StreamController controller;
|
| + Stream get stream => controller.stream;
|
| +
|
| + /// True when the async* function has yielded while being paused.
|
| + /// When true execution will only resume after a `onResume` or `onCancel`
|
| + /// event.
|
| + bool isSuspended = false;
|
| +
|
| + bool get isPaused => controller.isPaused;
|
| +
|
| + Completer cancelationCompleter = null;
|
| +
|
| + /// True after the StreamSubscription has been cancelled.
|
| + /// When this is true, errors thrown from the async* body should go to the
|
| + /// [cancelationCompleter] instead of adding them to [controller], and
|
| + /// returning from the async function should complete [cancelationCompleter].
|
| + bool get isCanceled => cancelationCompleter != null;
|
| +
|
| + add(event) => controller.add(event);
|
| +
|
| + addStream(Stream stream) {
|
| + return controller.addStream(stream, cancelOnError: false);
|
| + }
|
| +
|
| + addError(error, stackTrace) => controller.addError(error, stackTrace);
|
| +
|
| + close() => controller.close();
|
| +
|
| + _AsyncStarStreamController(_WrappedAsyncBody body) {
|
| +
|
| + _resumeBody() {
|
| + scheduleMicrotask(() {
|
| + body(async_error_codes.SUCCESS, null);
|
| + });
|
| + }
|
| +
|
| + controller = new StreamController(
|
| + onListen: () {
|
| + _resumeBody();
|
| + }, onResume: () {
|
| + // Only schedule again if the async* function actually is suspended.
|
| + // Resume directly instead of scheduling, so that the sequence
|
| + // `pause-resume-pause` will result in one extra event produced.
|
| + if (isSuspended) {
|
| + isSuspended = false;
|
| + _resumeBody();
|
| + }
|
| + }, onCancel: () {
|
| + // If the async* is finished we ignore cancel events.
|
| + if (!controller.isClosed) {
|
| + cancelationCompleter = new Completer();
|
| + if (isSuspended) {
|
| + // Resume the suspended async* function to run finalizers.
|
| + isSuspended = false;
|
| + scheduleMicrotask(() {
|
| + body(async_error_codes.STREAM_WAS_CANCELED, null);
|
| + });
|
| + }
|
| + return cancelationCompleter.future;
|
| + }
|
| + });
|
| + }
|
| +}
|
| +
|
| +_makeAsyncStarController(body) {
|
| + return new _AsyncStarStreamController(body);
|
| +}
|
| +
|
| +class _IterationMarker {
|
| + static const YIELD_SINGLE = 0;
|
| + static const YIELD_STAR = 1;
|
| + static const ITERATION_ENDED = 2;
|
| + static const UNCAUGHT_ERROR = 3;
|
| +
|
| + final value;
|
| + final int state;
|
| +
|
| + _IterationMarker._(this.state, this.value);
|
| +
|
| + static yieldStar(dynamic /* Iterable or Stream */ values) {
|
| + return new _IterationMarker._(YIELD_STAR, values);
|
| + }
|
| +
|
| + static endOfIteration() {
|
| + return new _IterationMarker._(ITERATION_ENDED, null);
|
| + }
|
| +
|
| + static yieldSingle(dynamic value) {
|
| + return new _IterationMarker._(YIELD_SINGLE, value);
|
| + }
|
| +
|
| + static uncaughtError(dynamic error) {
|
| + return new _IterationMarker._(UNCAUGHT_ERROR, error);
|
| + }
|
| +
|
| + toString() => "IterationMarker($state, $value)";
|
| +}
|
| +
|
| +class _SyncStarIterator implements Iterator {
|
| + final dynamic _body;
|
| +
|
| + // If [runningNested] this is the nested iterator, otherwise it is the
|
| + // current value.
|
| + dynamic _current = null;
|
| + bool _runningNested = false;
|
| +
|
| + get current => _runningNested ? _current.current : _current;
|
| +
|
| + _SyncStarIterator(this._body);
|
| +
|
| + _runBody() {
|
| + return JS('', '''
|
| +// Invokes [body] with [errorCode] and [result].
|
| +//
|
| +// If (and as long as) the invocation throws, calls [function] again,
|
| +// with an error-code.
|
| +(function(body) {
|
| + var errorValue, errorCode = #;
|
| + while (true) {
|
| + try {
|
| + return body(errorCode, errorValue);
|
| + } catch (error) {
|
| + errorValue = error;
|
| + errorCode = #
|
| + }
|
| + }
|
| +})(#)''', async_error_codes.SUCCESS, async_error_codes.ERROR, _body);
|
| + }
|
| +
|
| +
|
| + bool moveNext() {
|
| + if (_runningNested) {
|
| + if (_current.moveNext()) {
|
| + return true;
|
| + } else {
|
| + _runningNested = false;
|
| + }
|
| + }
|
| + _current = _runBody();
|
| + if (_current is _IterationMarker) {
|
| + if (_current.state == _IterationMarker.ITERATION_ENDED) {
|
| + _current = null;
|
| + // Rely on [_body] to repeatedly return `ITERATION_ENDED`.
|
| + return false;
|
| + } else if (_current.state == _IterationMarker.UNCAUGHT_ERROR) {
|
| + // Rely on [_body] to repeatedly return `UNCAUGHT_ERROR`.
|
| + // This is a wrapped exception, so we use JavaScript throw to throw it.
|
| + JS('', 'throw #', _current.value);
|
| + } 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 body with fresh state.
|
| + final dynamic /* js function */ _outerHelper;
|
| +
|
| + _SyncStarIterable(this._outerHelper);
|
| +
|
| + Iterator get iterator => new _SyncStarIterator(JS('', '#()', _outerHelper));
|
| +}
|
|
|