From d509ddb3fa9d2fb62dd2ef94010cd5dadbbd966b Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Fri, 31 Mar 2017 12:41:04 +0200 Subject: [PATCH] Use FutureOr more and make Future.sync return the resulting Future directly. When the computation passed to Future.sync returns a Future of the correct type, then it's returned directly instead of wrapping it again. (Until strong mode, we have an extra case for when it returns a Future of an incorrect type, but that will eventually be removed). This should improve the performance of Future.sync a bit. Also adds missing Zone intercept for when the Future.sync computation throws. Updates documentation for Future.doWhile. Fixes #29202 BUG= http://dartbug.com/29202 R=floitsch@google.com Review-Url: https://codereview.chromium.org/2790663003 . --- sdk/lib/async/future.dart | 60 +++++++++++++++++++------------- sdk/lib/async/future_impl.dart | 45 +++++++++++++----------- tests/lib/async/future_test.dart | 6 ++-- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/sdk/lib/async/future.dart b/sdk/lib/async/future.dart index ed0f1b74aba..5240bf8d5d7 100644 --- a/sdk/lib/async/future.dart +++ b/sdk/lib/async/future.dart @@ -37,7 +37,7 @@ part of dart.async; /// `FutureOr>`, `FutureOr> is equivalent to /// `Future`. abstract class FutureOr { - // Private constructor, so that it is not subclassable, mixable, or + // Private generative constructor, so that it is not subclassable, mixable, or // instantiable. FutureOr._() { throw new UnsupportedError("FutureOr can't be instantiated"); @@ -151,7 +151,7 @@ abstract class Future { * If a non-future value is returned, the returned future is completed * with that value. */ - factory Future(computation()) { + factory Future(FutureOr computation()) { _Future result = new _Future(); Timer.run(() { try { @@ -177,7 +177,7 @@ abstract class Future { * If calling [computation] returns a non-future value, * the returned future is completed with that value. */ - factory Future.microtask(computation()) { + factory Future.microtask(FutureOr computation()) { _Future result = new _Future(); scheduleMicrotask(() { try { @@ -190,38 +190,51 @@ abstract class Future { } /** - * Creates a future containing the result of immediately calling + * Returns a future containing the result of immediately calling * [computation]. * * If calling [computation] throws, the returned future is completed with the * error. * - * If calling [computation] returns a [Future], completion of - * the created future will wait until the returned future completes, - * and will then complete with the same result. + * If calling [computation] returns a `Future`, that future is returned. * * If calling [computation] returns a non-future value, - * the returned future is completed with that value. + * a future is returned which has been completed with that value. */ - factory Future.sync(computation()) { + factory Future.sync(FutureOr computation()) { try { var result = computation(); - return new Future.value(result); + if (result is Future) { + return result; + } else if (result is Future) { + // TODO(lrn): Remove this case for Dart 2.0. + return new _Future.immediate(result); + } else { + return new _Future.value(result); + } } catch (error, stackTrace) { - return new Future.error(error, stackTrace); + var future = new _Future(); + AsyncError replacement = Zone.current.errorCallback(error, stackTrace); + if (replacement != null) { + future._asyncCompleteError( + _nonNullError(replacement.error), replacement.stackTrace); + } else { + future._asyncCompleteError(error, stackTrace); + } + return future; } } /** * A future whose value is available in the next event-loop iteration. * - * If [value] is not a [Future], using this constructor is equivalent - * to [:new Future.sync(() => value):]. + * If [result] is not a [Future], using this constructor is equivalent + * to `new Future.sync(() => result)`. * - * Use [Completer] to create a Future and complete it later. + * Use [Completer] to create a future and complete it later. */ - factory Future.value([value]) { - return new _Future.immediate(value); + factory Future.value([FutureOr result]) { + return new _Future.immediate(result); } /** @@ -422,7 +435,7 @@ abstract class Future { * If [f] returns a non-[Future], iteration continues immediately. Otherwise * it waits for the returned [Future] to complete. */ - static Future forEach(Iterable input, dynamic f(T element)) { + static Future forEach(Iterable input, FutureOr f(T element)) { var iterator = input.iterator; return doWhile(() { if (!iterator.moveNext()) return false; @@ -437,13 +450,12 @@ abstract class Future { * value `true` or a [Future] which completes with the value `true`. * * If a call to [f] returns `false` or a [Future] that completes to `false`, - * iteration ends and the future returned by [doWhile] is completed. + * iteration ends and the future returned by [doWhile] is completed with + * a `null` value. * - * If a future returned by [f] completes with an error, iteration ends and - * the future returned by [doWhile] completes with the same error. - * - * The [f] function must return either a `bool` value or a [Future] completing - * with a `bool` value. + * If a call to [f] throws or a future returned by [f] completes with + * an error, iteration ends and the future returned by [doWhile] + * completes with the same error. */ static Future doWhile(FutureOr f()) { _Future doneSignal = new _Future(); @@ -609,7 +621,7 @@ abstract class Future { * }); * } */ - Future whenComplete(dynamic action()); + Future whenComplete(FutureOr action()); /** * Creates a [Stream] containing the result of this future. diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart index 325c5d715b4..14bac61ce77 100644 --- a/sdk/lib/async/future_impl.dart +++ b/sdk/lib/async/future_impl.dart @@ -207,15 +207,19 @@ class _Future implements Future { // This constructor is used by async/await. _Future(); - /// Valid types for value: `T` or `Future`. - _Future.immediate(value) { - _asyncComplete(value); + _Future.immediate(FutureOr result) { + _asyncComplete(result); } _Future.immediateError(var error, [StackTrace stackTrace]) { _asyncCompleteError(error, stackTrace); } + /** Creates a future that is already completed with the value. */ + _Future.value(T value) { + _setValue(value); + } + bool get _mayComplete => _state == _INCOMPLETE; bool get _isPendingComplete => _state == _PENDING_COMPLETE; bool get _mayAddListener => _state <= _PENDING_COMPLETE; @@ -496,23 +500,7 @@ class _Future implements Future { // it. if (value is Future) { - if (value is _Future) { - if (value._hasError) { - // Case 1 from above. Delay completion to enable the user to register - // callbacks. - _setPendingComplete(); - _zone.scheduleMicrotask(() { - _chainCoreFuture(value, this); - }); - } else { - _chainCoreFuture(value, this); - } - } else { - // Case 2 from above. Chain the future immediately. - // Note that we are still completing asynchronously (through - // _chainForeignFuture). - _chainForeignFuture(value, this); - } + _chainFuture(value); return; } T typedValue = value as Object/*=T*/; @@ -523,6 +511,23 @@ class _Future implements Future { }); } + void _chainFuture(Future value) { + if (value is _Future) { + if (value._hasError) { + // Delay completion to allow the user to register callbacks. + _setPendingComplete(); + _zone.scheduleMicrotask(() { + _chainCoreFuture(value, this); + }); + } else { + _chainCoreFuture(value, this); + } + return; + } + // Just listen on the foreign future. This guarantees an async delay. + _chainForeignFuture(value, this); + } + void _asyncCompleteError(error, StackTrace stackTrace) { assert(!_isComplete); diff --git a/tests/lib/async/future_test.dart b/tests/lib/async/future_test.dart index 5d84ff15f76..efe66e25c32 100644 --- a/tests/lib/async/future_test.dart +++ b/tests/lib/async/future_test.dart @@ -994,9 +994,9 @@ void testTypes() { new Future.delayed(Duration.ZERO, () => value)); testType( "Future.microtask($value)", new Future.microtask(() => value)); - testType("Future.sync($value)", new Future.sync(() => value)); // //# 01: ok - testType("Future.sync(future($value))", // //# 01: continued - new Future.sync(() async => new Future.value(value))); //# 01: continued + testType("Future.sync($value)", new Future.sync(() => value)); // //# 01: ok + testType("Future.sync(future($value))", // //# 01: continued + new Future.sync(() async => new Future.value(value))); //# 01: continued testType("Future.value($value)", new Future.value(value)); } testType("Completer.future", new Completer().future);