Index: sdk/lib/_internal/compiler/js_lib/js_helper.dart |
=================================================================== |
--- sdk/lib/_internal/compiler/js_lib/js_helper.dart (revision 43839) |
+++ sdk/lib/_internal/compiler/js_lib/js_helper.dart (working copy) |
@@ -4,6 +4,8 @@ |
library _js_helper; |
+import 'dart:_async_await_error_codes' as async_error_codes; |
+ |
import 'dart:_js_embedded_names' show |
GET_TYPE_FROM_NAME, |
GET_ISOLATE_TAG, |
@@ -26,13 +28,13 @@ |
leaveJsAsync; |
import 'dart:async' show |
- Future, |
- DeferredLoadException, |
- Completer, |
- StreamController, |
- Stream, |
- StreamSubscription, |
- scheduleMicrotask; |
+ Future, |
+ DeferredLoadException, |
+ Completer, |
+ StreamController, |
+ Stream, |
+ StreamSubscription, |
+ scheduleMicrotask; |
import 'dart:_foreign_helper' show |
DART_CLOSURE_TO_JS, |
@@ -3477,59 +3479,62 @@ |
/// |
/// 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. |
+/// 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 [helperCallback] is set up to be called when the future is |
-/// successfull and [errorCallback] if it is completed with an error. |
+/// 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. |
/// |
-/// 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) { |
+dynamic asyncHelper(dynamic object, |
+ dynamic /* js function */ 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; |
} |
Future future = object is Future ? object : new Future.value(object); |
- future.then(_wrapJsFunctionForThenHelper(helperCallback, completer), |
- onError: (errorCallback == null) |
- ? null |
- : _wrapJsFunctionForThenHelper(errorCallback, completer)); |
+ future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.SUCCESS), |
+ onError: _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.ERROR)); |
return completer.future; |
} |
-Function _wrapJsFunctionForThenHelper(dynamic /* js function */ function, |
- Completer completer) { |
+Function _wrapJsFunctionForAsync(dynamic /* js function */ function, |
+ int errorCode) { |
return (result) { |
- try { |
- JS('', '#(#)', function, result); |
- } catch (e, st) { |
- completer.completeError(e, st); |
- } |
+ JS('', '#(#, #)', function, 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 |
-/// [helperCallback] == null, the streamHelper takes this as signal to close the |
-/// stream. |
+/// 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. |
/// |
-/// If the async* function wants to do a yield or yield* it calls this function |
-/// with [object] being an [IterationMarker]. In this case [errorCallback] has a |
-/// special meaning; it is a callback that will run all enclosing finalizers. |
+/// 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 [errorCallback] is scheduled. |
+/// 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 |
@@ -3539,30 +3544,32 @@ |
/// 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 [helperCallback] again. |
+/// 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 [helperCallback] is called on successfull completion of the |
-/// future. |
-/// |
-/// If [helperCallback] or [errorCallback] throws the error is added to the |
-/// stream. |
-void streamHelper(dynamic object, |
- dynamic /* js function */ helperCallback, |
- AsyncStarStreamController controller, |
- dynamic /* js function */ errorCallback) { |
- if (helperCallback == null) { |
+/// The [asyncBody] is called on completion of the future (see [asyncHelper]. |
+void asyncStarHelper(dynamic object, |
+ dynamic /* int | js function */ bodyFunctionOrErrorCode, |
+ AsyncStarStreamController controller) { |
+ if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) { |
// This happens on return from the async* function. |
controller.close(); |
return; |
+ } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) { |
+ // The error is a js-error. |
+ controller.addError(unwrapException(object), |
+ getTraceFromException(object)); |
+ controller.close(); |
+ return; |
} |
if (object is IterationMarker) { |
if (controller.stopRunning) { |
- _wrapJsFunctionForStream(errorCallback, controller)(); |
+ _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.STREAM_WAS_CANCELED)(null); |
return; |
} |
if (object.state == IterationMarker.YIELD_SINGLE) { |
@@ -3573,7 +3580,9 @@ |
} |
// TODO(sigurdm): We should not suspend here according to the spec. |
scheduleMicrotask(() { |
- _wrapJsFunctionForStream(helperCallback, controller)(null); |
+ _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.SUCCESS) |
+ (null); |
}); |
return; |
} else if (object.state == IterationMarker.YIELD_STAR) { |
@@ -3584,7 +3593,8 @@ |
// TODO(sigurdm): The spec is not very clear here. Clarify with Gilad. |
controller.addStream(stream).then((_) { |
controller.isAdding = false; |
- _wrapJsFunctionForStream(helperCallback, controller)(null); |
+ _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.SUCCESS)(null); |
}); |
return; |
} |
@@ -3591,10 +3601,10 @@ |
} |
Future future = object is Future ? object : new Future.value(object); |
- future.then(_wrapJsFunctionForStream(helperCallback, controller), |
- onError: errorCallback == null |
- ? null |
- : _wrapJsFunctionForStream(errorCallback, controller)); |
+ future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.SUCCESS), |
+ onError: _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
+ async_error_codes.ERROR)); |
} |
Stream streamOfController(AsyncStarStreamController controller) { |
@@ -3622,11 +3632,13 @@ |
AsyncStarStreamController(helperCallback) { |
controller = new StreamController( |
onListen: () { |
- scheduleMicrotask(() => JS('', '#(null)', helperCallback)); |
+ scheduleMicrotask(() { |
+ JS('', '#(#, null)', helperCallback, async_error_codes.SUCCESS); |
+ }); |
}, |
onResume: () { |
if (!isAdding) { |
- streamHelper(null, helperCallback, this, null); |
+ asyncStarHelper(null, helperCallback, this); |
} |
}, onCancel: () { |
stopRunning = true; |
@@ -3638,22 +3650,11 @@ |
return new AsyncStarStreamController(helperCallback); |
} |
-Function _wrapJsFunctionForStream(dynamic /* js function */ function, |
- AsyncStarStreamController controller) { |
- return (result) { |
- try { |
- JS('', '#(#)', function, result); |
- } catch (e, st) { |
- controller.addError(e, st); |
- } |
- }; |
-} |
- |
- |
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; |
@@ -3672,11 +3673,15 @@ |
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 Function _helper; |
+ final Function _body; |
// If [runningNested] this is the nested iterator, otherwise it is the |
// current value. |
@@ -3685,8 +3690,8 @@ |
get current => _runningNested ? _current.current : _current; |
- SyncStarIterator(helper) |
- : _helper = ((arg) => JS('', '#(#)', helper, arg)); |
+ SyncStarIterator(body) |
+ : _body = (() => JS('', '#()', body)); |
bool moveNext() { |
if (_runningNested) { |
@@ -3696,12 +3701,16 @@ |
_runningNested = false; |
} |
} |
- _current = _helper(null); |
+ _current = _body(); |
if (_current is IterationMarker) { |
if (_current.state == IterationMarker.ITERATION_ENDED) { |
_current = null; |
- // Rely on [_helper] to repeatedly return `ITERATION_ENDED`. |
+ // 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; |
@@ -3720,7 +3729,7 @@ |
// 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. |
+ // Each invocation should give a body with fresh state. |
final dynamic /* js function */ _outerHelper; |
SyncStarIterable(this._outerHelper); |