OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 library _js_helper; | 5 library _js_helper; |
6 | 6 |
7 import 'dart:_async_await_error_codes' as async_error_codes; | 7 import 'dart:_async_await_error_codes' as async_error_codes; |
8 | 8 |
9 import 'dart:_js_embedded_names' show | 9 import 'dart:_js_embedded_names' show |
10 DEFERRED_LIBRARY_URIS, | 10 DEFERRED_LIBRARY_URIS, |
(...skipping 3624 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3635 } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) { | 3635 } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) { |
3636 // The error is a js-error. | 3636 // The error is a js-error. |
3637 completer.completeError(unwrapException(object), | 3637 completer.completeError(unwrapException(object), |
3638 getTraceFromException(object)); | 3638 getTraceFromException(object)); |
3639 return; | 3639 return; |
3640 } | 3640 } |
3641 Future future = object is Future ? object : new Future.value(object); | 3641 Future future = object is Future ? object : new Future.value(object); |
3642 future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3642 future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
3643 async_error_codes.SUCCESS), | 3643 async_error_codes.SUCCESS), |
3644 onError: (dynamic error, StackTrace stackTrace) { | 3644 onError: (dynamic error, StackTrace stackTrace) { |
3645 ExceptionAndStackTrace wrapped = | 3645 ExceptionAndStackTrace wrappedException = |
3646 new ExceptionAndStackTrace(error, stackTrace); | 3646 new ExceptionAndStackTrace(error, stackTrace); |
3647 return _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3647 Function wrapped =_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
3648 async_error_codes.ERROR)(wrapped); | 3648 async_error_codes.ERROR); |
| 3649 wrapped(wrappedException); |
3649 }); | 3650 }); |
3650 return completer.future; | 3651 return completer.future; |
3651 } | 3652 } |
3652 | 3653 |
3653 Function _wrapJsFunctionForAsync(dynamic /* js function */ function, | 3654 Function _wrapJsFunctionForAsync(dynamic /* js function */ function, |
3654 int errorCode) { | 3655 int errorCode) { |
3655 var protected = JS('', """ | 3656 var protected = JS('', """ |
3656 // Invokes [function] with [errorCode] and [result]. | 3657 // Invokes [function] with [errorCode] and [result]. |
3657 // | 3658 // |
3658 // If (and as long as) the invocation throws, calls [function] again, | 3659 // If (and as long as) the invocation throws, calls [function] again, |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3707 /// If the async* function wants to do an await it calls this function with | 3708 /// If the async* function wants to do an await it calls this function with |
3708 /// [object] not and [IterationMarker]. | 3709 /// [object] not and [IterationMarker]. |
3709 /// | 3710 /// |
3710 /// If [object] is not a [Future], it is wrapped in a `Future.value`. | 3711 /// If [object] is not a [Future], it is wrapped in a `Future.value`. |
3711 /// The [asyncBody] is called on completion of the future (see [asyncHelper]. | 3712 /// The [asyncBody] is called on completion of the future (see [asyncHelper]. |
3712 void asyncStarHelper(dynamic object, | 3713 void asyncStarHelper(dynamic object, |
3713 dynamic /* int | js function */ bodyFunctionOrErrorCode, | 3714 dynamic /* int | js function */ bodyFunctionOrErrorCode, |
3714 AsyncStarStreamController controller) { | 3715 AsyncStarStreamController controller) { |
3715 if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) { | 3716 if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) { |
3716 // This happens on return from the async* function. | 3717 // This happens on return from the async* function. |
3717 if (controller.cancelationCompleter != null) { | 3718 if (controller.isCanceled) { |
3718 controller.cancelationCompleter.complete(); | 3719 controller.cancelationCompleter.complete(); |
3719 } else { | 3720 } else { |
3720 controller.close(); | 3721 controller.close(); |
3721 } | 3722 } |
3722 return; | 3723 return; |
3723 } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) { | 3724 } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) { |
3724 // The error is a js-error. | 3725 // The error is a js-error. |
3725 if (controller.cancelationCompleter != null) { | 3726 if (controller.isCanceled) { |
3726 controller.cancelationCompleter.completeError( | 3727 controller.cancelationCompleter.completeError( |
3727 unwrapException(object), | 3728 unwrapException(object), |
3728 getTraceFromException(object)); | 3729 getTraceFromException(object)); |
3729 } else { | 3730 } else { |
3730 controller.addError(unwrapException(object), | 3731 controller.addError(unwrapException(object), |
3731 getTraceFromException(object)); | 3732 getTraceFromException(object)); |
3732 controller.close(); | 3733 controller.close(); |
3733 } | 3734 } |
3734 return; | 3735 return; |
3735 } | 3736 } |
3736 | 3737 |
3737 if (object is IterationMarker) { | 3738 if (object is IterationMarker) { |
3738 if (controller.cancelationCompleter != null) { | 3739 if (controller.isCanceled) { |
3739 _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3740 Function wrapped = _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
3740 async_error_codes.STREAM_WAS_CANCELED)(null); | 3741 async_error_codes.STREAM_WAS_CANCELED); |
| 3742 wrapped(null); |
3741 return; | 3743 return; |
3742 } | 3744 } |
3743 if (object.state == IterationMarker.YIELD_SINGLE) { | 3745 if (object.state == IterationMarker.YIELD_SINGLE) { |
3744 controller.add(object.value); | 3746 controller.add(object.value); |
3745 // If the controller is paused we stop producing more values. | 3747 |
3746 if (controller.isPaused) { | |
3747 return; | |
3748 } | |
3749 // TODO(sigurdm): We should not suspend here according to the spec. | |
3750 scheduleMicrotask(() { | 3748 scheduleMicrotask(() { |
3751 _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3749 if (controller.isPaused) { |
3752 async_error_codes.SUCCESS) | 3750 // We only suspend the thread inside the microtask in order to allow |
3753 (null); | 3751 // listeners on the output stream to pause in response to the just |
| 3752 // output value, and have the stream immediately stop producing. |
| 3753 controller.isSuspended = true; |
| 3754 return; |
| 3755 } |
| 3756 Function wrapped = _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
| 3757 async_error_codes.SUCCESS); |
| 3758 wrapped(null); |
3754 }); | 3759 }); |
3755 return; | 3760 return; |
3756 } else if (object.state == IterationMarker.YIELD_STAR) { | 3761 } else if (object.state == IterationMarker.YIELD_STAR) { |
3757 Stream stream = object.value; | 3762 Stream stream = object.value; |
3758 controller.isAdding = true; | |
3759 // Errors of [stream] are passed though to the main stream. (see | 3763 // Errors of [stream] are passed though to the main stream. (see |
3760 // [AsyncStreamController.addStream]. | 3764 // [AsyncStreamController.addStream]). |
3761 // TODO(sigurdm): The spec is not very clear here. Clarify with Gilad. | 3765 // TODO(sigurdm): The spec is not very clear here. Clarify with Gilad. |
3762 controller.addStream(stream).then((_) { | 3766 controller.addStream(stream).then((_) { |
3763 controller.isAdding = false; | 3767 // No check for isPaused here because the spec 17.16.2 only |
3764 _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3768 // demands checks *before* each element in [stream] not after the last |
3765 async_error_codes.SUCCESS)(null); | 3769 // one. On the other hand we check for isCanceled, as that check happens |
| 3770 // after insertion of each element. |
| 3771 int errorCode = controller.isCanceled |
| 3772 ? async_error_codes.STREAM_WAS_CANCELED |
| 3773 : async_error_codes.SUCCESS; |
| 3774 Function wrapped = _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
| 3775 errorCode); |
| 3776 wrapped(null); |
3766 }); | 3777 }); |
3767 return; | 3778 return; |
3768 } | 3779 } |
3769 } | 3780 } |
3770 | 3781 |
3771 Future future = object is Future ? object : new Future.value(object); | 3782 Future future = object is Future ? object : new Future.value(object); |
3772 future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3783 future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode, |
3773 async_error_codes.SUCCESS), | 3784 async_error_codes.SUCCESS), |
3774 onError: (error, StackTrace stackTrace) { | 3785 onError: (error, StackTrace stackTrace) { |
3775 ExceptionAndStackTrace wrapped = | 3786 ExceptionAndStackTrace wrappedException = |
3776 new ExceptionAndStackTrace(error, stackTrace); | 3787 new ExceptionAndStackTrace(error, stackTrace); |
3777 return _wrapJsFunctionForAsync(bodyFunctionOrErrorCode, | 3788 Function wrapped = _wrapJsFunctionForAsync( |
3778 async_error_codes.ERROR) | 3789 bodyFunctionOrErrorCode, async_error_codes.ERROR); |
3779 (wrapped); | 3790 return wrapped(wrappedException); |
3780 }); | 3791 }); |
3781 } | 3792 } |
3782 | 3793 |
3783 Stream streamOfController(AsyncStarStreamController controller) { | 3794 Stream streamOfController(AsyncStarStreamController controller) { |
3784 return controller.stream; | 3795 return controller.stream; |
3785 } | 3796 } |
3786 | 3797 |
3787 /// A wrapper around a [StreamController] that remembers if that controller | 3798 /// A wrapper around a [StreamController] that keeps track of the state of |
3788 /// got a cancel. | 3799 /// the execution of an async* function. |
| 3800 /// It can be in 1 of 3 states: |
3789 /// | 3801 /// |
3790 /// Also has a subSubscription that when not null will provide events for the | 3802 /// - running/scheduled |
3791 /// stream, and will be paused and resumed along with this controller. | 3803 /// - suspended |
| 3804 /// - canceled |
| 3805 /// |
| 3806 /// If yielding while the subscription is paused it will become suspended. And |
| 3807 /// only resume after the subscription is resumed or canceled. |
3792 class AsyncStarStreamController { | 3808 class AsyncStarStreamController { |
3793 StreamController controller; | 3809 StreamController controller; |
3794 Stream get stream => controller.stream; | 3810 Stream get stream => controller.stream; |
| 3811 |
| 3812 /// True when the async* function has yielded while being paused. |
| 3813 /// When true execution will only resume after a `onResume` or `onCancel` |
| 3814 /// event. |
| 3815 bool isSuspended = false; |
| 3816 |
| 3817 bool get isPaused => controller.isPaused; |
| 3818 |
3795 Completer cancelationCompleter = null; | 3819 Completer cancelationCompleter = null; |
| 3820 |
| 3821 /// True after the StreamSubscription has been cancelled. |
| 3822 /// When this is true, errors thrown from the async* body should go to the |
| 3823 /// [cancelationCompleter] instead of adding them to [controller], and |
| 3824 /// returning from the async function should complete [cancelationCompleter]. |
3796 bool get isCanceled => cancelationCompleter != null; | 3825 bool get isCanceled => cancelationCompleter != null; |
3797 bool isAdding = false; | 3826 |
3798 bool isPaused = false; | |
3799 add(event) => controller.add(event); | 3827 add(event) => controller.add(event); |
| 3828 |
3800 addStream(Stream stream) { | 3829 addStream(Stream stream) { |
3801 return controller.addStream(stream, cancelOnError: false); | 3830 return controller.addStream(stream, cancelOnError: false); |
3802 } | 3831 } |
| 3832 |
3803 addError(error, stackTrace) => controller.addError(error, stackTrace); | 3833 addError(error, stackTrace) => controller.addError(error, stackTrace); |
| 3834 |
3804 close() => controller.close(); | 3835 close() => controller.close(); |
3805 | 3836 |
3806 AsyncStarStreamController(body) { | 3837 AsyncStarStreamController(body) { |
| 3838 |
| 3839 _resumeBody() { |
| 3840 scheduleMicrotask(() { |
| 3841 Function wrapped = |
| 3842 _wrapJsFunctionForAsync(body, async_error_codes.SUCCESS); |
| 3843 wrapped(null); |
| 3844 }); |
| 3845 } |
| 3846 |
3807 controller = new StreamController( | 3847 controller = new StreamController( |
3808 onListen: () { | 3848 onListen: () { |
3809 scheduleMicrotask(() { | 3849 _resumeBody(); |
3810 Function wrapped = _wrapJsFunctionForAsync(body, | |
3811 async_error_codes.SUCCESS); | |
3812 wrapped(null); | |
3813 }); | |
3814 }, | |
3815 onPause: () { | |
3816 isPaused = true; | |
3817 }, onResume: () { | 3850 }, onResume: () { |
3818 isPaused = false; | 3851 // Only schedule again if the async* function actually is suspended. |
3819 if (!isAdding) { | 3852 // Resume directly instead of scheduling, so that the sequence |
3820 asyncStarHelper(null, body, this); | 3853 // `pause-resume-pause` will result in one extra event produced. |
| 3854 if (isSuspended) { |
| 3855 isSuspended = false; |
| 3856 _resumeBody(); |
3821 } | 3857 } |
3822 }, onCancel: () { | 3858 }, onCancel: () { |
| 3859 // If the async* is finished we ignore cancel events. |
3823 if (!controller.isClosed) { | 3860 if (!controller.isClosed) { |
3824 cancelationCompleter = new Completer(); | 3861 cancelationCompleter = new Completer(); |
3825 if (isPaused) asyncStarHelper(null, body, this); | 3862 if (isSuspended) { |
3826 | 3863 // Resume the suspended async* function to run finalizers. |
| 3864 isSuspended = false; |
| 3865 scheduleMicrotask(() { |
| 3866 Function wrapped =_wrapJsFunctionForAsync(body, |
| 3867 async_error_codes.STREAM_WAS_CANCELED); |
| 3868 wrapped(null); |
| 3869 }); |
| 3870 } |
3827 return cancelationCompleter.future; | 3871 return cancelationCompleter.future; |
3828 } | 3872 } |
3829 }); | 3873 }); |
3830 } | 3874 } |
3831 } | 3875 } |
3832 | 3876 |
3833 makeAsyncStarController(body) { | 3877 makeAsyncStarController(body) { |
3834 return new AsyncStarStreamController(body); | 3878 return new AsyncStarStreamController(body); |
3835 } | 3879 } |
3836 | 3880 |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3932 // This is a function that will return a helper function that does the | 3976 // This is a function that will return a helper function that does the |
3933 // iteration of the sync*. | 3977 // iteration of the sync*. |
3934 // | 3978 // |
3935 // Each invocation should give a body with fresh state. | 3979 // Each invocation should give a body with fresh state. |
3936 final dynamic /* js function */ _outerHelper; | 3980 final dynamic /* js function */ _outerHelper; |
3937 | 3981 |
3938 SyncStarIterable(this._outerHelper); | 3982 SyncStarIterable(this._outerHelper); |
3939 | 3983 |
3940 Iterator get iterator => new SyncStarIterator(JS('', '#()', _outerHelper)); | 3984 Iterator get iterator => new SyncStarIterator(JS('', '#()', _outerHelper)); |
3941 } | 3985 } |
OLD | NEW |