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