From e4f12438a4ff33085d5991adc0679a8c73c34353 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Fri, 24 Sep 2021 13:07:40 +0000 Subject: [PATCH] Add documentation on `Future` and `Stream` showing `async` programming. The documentation on `Future` and `Stream` was only showing the class API, not `async` programming. Added examples of using `async`, `async*`, `await`, `await for`, `yield` and `yield*`. Fixes #47082 Bug: http://dartbug.com/47082 Change-Id: Ib8b4e8642412073e9e6124429d66747c277f89a6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212470 Commit-Queue: Lasse R.H. Nielsen Reviewed-by: Nate Bosch --- .../nnbd/issue42546.dart.weak.outline.expect | 30 ++-- sdk/lib/async/future.dart | 130 ++++++++++++++---- sdk/lib/async/stream.dart | 72 ++++++++-- 3 files changed, 183 insertions(+), 49 deletions(-) diff --git a/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect index 08204a37087..d87cbdc27bc 100644 --- a/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect +++ b/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect @@ -28,19 +28,19 @@ static method main() → dynamic Extra constant evaluation status: -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:639:13 -> SymbolConstant(#catchError) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:639:13 -> ListConstant(const []) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:639:13 -> SymbolConstant(#test) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:675:13 -> SymbolConstant(#whenComplete) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:675:13 -> ListConstant(const []) -Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:675:13 -> MapConstant(const {}) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:698:13 -> SymbolConstant(#timeout) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:698:13 -> ListConstant(const []) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:698:13 -> SymbolConstant(#onTimeout) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:602:13 -> SymbolConstant(#then) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:602:13 -> SymbolConstant(#onError) -Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> SymbolConstant(#asStream) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> ListConstant(const []) -Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> ListConstant(const []) -Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> MapConstant(const {}) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:717:13 -> SymbolConstant(#catchError) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:717:13 -> ListConstant(const []) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:717:13 -> SymbolConstant(#test) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:753:13 -> SymbolConstant(#whenComplete) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:753:13 -> ListConstant(const []) +Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:753:13 -> MapConstant(const {}) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:776:13 -> SymbolConstant(#timeout) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:776:13 -> ListConstant(const []) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:776:13 -> SymbolConstant(#onTimeout) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:680:13 -> SymbolConstant(#then) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:680:13 -> SymbolConstant(#onError) +Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> SymbolConstant(#asStream) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> ListConstant(const []) +Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> ListConstant(const []) +Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> MapConstant(const {}) Extra constant evaluation: evaluated: 61, effectively constant: 15 diff --git a/sdk/lib/async/future.dart b/sdk/lib/async/future.dart index 555bc3dd4c0..e43b92a8dcd 100644 --- a/sdk/lib/async/future.dart +++ b/sdk/lib/async/future.dart @@ -7,8 +7,8 @@ part of dart.async; /// A type representing values that are either `Future` or `T`. /// /// This class declaration is a public stand-in for an internal -/// future-or-value generic type. References to this class are resolved to the -/// internal type. +/// future-or-value generic type, which is not a class type. +/// References to this class are resolved to the internal type. /// /// It is a compile-time error for any class to extend, mix in or implement /// `FutureOr`. @@ -43,35 +43,109 @@ abstract class FutureOr { } } -/// An object representing a delayed computation. +/// The result of an asynchronous computation. /// -/// A [Future] is used to represent a potential value, or error, -/// that will be available at some time in the future. -/// Receivers of a [Future] can register callbacks -/// that handle the value or error once it is available. +/// An _asynchronous computation_ cannot provide a result immediately +/// when it is started, unlike a synchronous computation which does compute +/// a result immediately by either returning a value or by throwing. +/// An asynchronous computation may need to wait for something external +/// to the program (reading a file, querying a database, fetching a web page) +/// which takes time. +/// Instead of blocking all computation until the result is available, +/// the asynchronous computation immediately returns a `Future` +/// which will *eventually* "complete" with the result. +/// +/// ### Asynchronous programming +/// +/// To perform an asynchronous computation, you use an `async` function +/// which always produces a future. +/// Inside such an asynchronous function, you can use the `await` operation +/// to delay execution until another asyncronous computation has a result. +/// While execution of the awaiting function is delayed, +/// the program is not blocked, and can continue doing other things. +/// +/// Example: +/// ```dart +/// import "dart:io"; +/// Future fileContains(String path, String needle) async { +/// var haystack = await File(path).readAsString(); +/// return haystack.contains(needle); +/// } +/// ``` +/// Here the `File.readAsString` method from `dart:io` is an asychronous +/// function returning a `Future`. +/// The `fileContains` function is marked with `async` right before its body, +/// which means that you can use `await` insider it, +/// and that it must return a future. +/// The call to `File(path).readAsString()` initiates reading the file into +/// a string and produces a `Future` which will eventually contain the +/// result. +/// The `await` then waits for that future to complete with a string +/// (or an error, if reading the file fails). +/// While waiting, the program can do other things. +/// When the future completes with a string, the `fileContains` function +/// computes a boolean and returns it, which then completes the original +/// future that it returned when first called. +/// +/// If a future completes with an *error*, awaiting that future will +/// (re-)throw that error. In the example here, we can add error checking: +/// ```dart +/// import "dart:io"; +/// Future fileContains(String path, String needle) async { +/// try { +/// var haystack = await File(path).readAsString(); +/// return haystack.contains(needle); +/// } on FileSystemException catch (exception, stack) { +/// _myLog.logError(exception, stack); +/// return false; +/// } +/// } +/// ``` +/// You use a normal `try`/`catch` to catch the failures of awaited +/// asynchronous computations. +/// +/// In general, when writing asynchronous code, you should always await a +/// future when it is produced, and not wait until after another asynchronous +/// delay. That ensures that you are ready to receive any error that the +/// future might produce, which is important because an asynchronous error +/// that no-one is awaiting is an *uncaught* error and may terminate +/// the running program. +/// +/// ### Programming with the `Future` API. +/// +/// The `Future` class also provides a more direct, low-level functionality +/// for accessing the result that it completes with. +/// The `async` and `await` language features are built on top of this +/// functionality, and it sometimes makes sense to use it directly. +/// There are things that you cannot do by just `await`ing one future at +/// a time. +/// +/// With a [Future], you can manually register callbacks +/// that handle the value, or error, once it is available. /// For example: /// ```dart /// Future future = getFuture(); /// future.then((value) => handleValue(value)) /// .catchError((error) => handleError(error)); /// ``` -/// A [Future] can be completed in two ways: -/// with a value ("the future succeeds") -/// or with an error ("the future fails"). -/// Users can install callbacks for each case. +/// Since a [Future] can be completed in two ways, +/// either with a value (if the asynchronous computation succeeded) +/// or with an error (if the computation failed), +/// you can install callbacks for either or both cases. /// -/// In some cases we say that a future is completed with another future. +/// In some cases we say that a future is completed *with another future*. /// This is a short way of stating that the future is completed in the same way, /// with the same value or error, -/// as the other future once that completes. -/// Whenever a function in the core library may complete a future +/// as the other future once that other future itself completes. +/// Most functions in the platform libraries that complete a future /// (for example [Completer.complete] or [Future.value]), -/// then it also accepts another future and does this work for the developer. +/// also accepts another future, and automatically handles forwarding +/// the result to the future being completed. /// -/// The result of registering a pair of callbacks is a Future (the -/// "successor") which in turn is completed with the result of invoking the -/// corresponding callback. -/// The successor is completed with an error if the invoked callback throws. +/// The result of registering callbacks is itself a `Future`, +/// which in turn is completed with the result of invoking the +/// corresponding callback with the original future's result. +/// The new future is completed with an error if the invoked callback throws. /// For example: /// ```dart /// Future successor = future.then((int value) { @@ -88,9 +162,9 @@ abstract class FutureOr { /// }); /// ``` /// -/// If a future does not have a successor when it completes with an error, -/// it forwards the error message to an uncaught-error handler. -/// This behavior makes sure that no error is silently dropped. +/// If a future does not have any registered handler when it completes +/// with an error, it forwards the error to an "uncaught-error handler". +/// This behavior ensures that no error is silently dropped. /// However, it also means that error handlers should be installed early, /// so that they are present as soon as a future is completed with an error. /// The following example demonstrates this potential bug: @@ -128,9 +202,12 @@ abstract class FutureOr { /// /// Equivalent asynchronous code, based on futures: /// ```dart -/// Future future = Future(foo); // Result of foo() as a future. -/// future.then((int value) => bar(value)) -/// .catchError((e) => 499); +/// Future asyncValue = Future(foo); // Result of foo() as a future. +/// asyncValue.then((int value) { +/// return bar(value); +/// }).catchError((e) { +/// return 499; +/// }); /// ``` /// /// Similar to the synchronous code, the error handler (registered with @@ -142,7 +219,8 @@ abstract class FutureOr { /// treated independently and is handled as if it was the only successor. /// /// A future may also fail to ever complete. In that case, no callbacks are -/// called. +/// called. That situation should generally be avoided if possible, unless +/// it's very clearly documented. abstract class Future { /// A `Future` completed with `null`. /// diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart index 7a3c80da05e..208351026d8 100644 --- a/sdk/lib/async/stream.dart +++ b/sdk/lib/async/stream.dart @@ -18,9 +18,61 @@ typedef void _TimerCallback(); /// When a stream has emitted all its event, /// a single "done" event will notify the listener that the end has been reached. /// -/// You [listen] on a stream to make it start generating events, -/// and to set up listeners that receive the events. -/// When you listen, you receive a [StreamSubscription] object +/// You produce a stream by calling an `async*` function, which then returns +/// a stream. Consuming that stream will lead the function to emit events +/// until it ends, and the stream closes. +/// You consume a stream either using an `await for` loop, which is available +/// inside an `async` or `async*` function, or forwards its events directly +/// using `yield*` inside an `async*` function. +/// Example: +/// ```dart +/// Stream optionalMap( +/// Stream source , [T Function(T)? convert]) async* { +/// if (convert == null) { +/// yield* source; +/// } else { +/// await for (var event in source) { +/// yield convert(event); +/// } +/// } +/// } +/// ``` +/// When this function is called, it immediately returns a `Stream` object. +/// Then nothing further happens until someone tries to consume that stream. +/// At that point, the body of the `async*` function starts running. +/// If the `convert` function was omitted, the `yield*` will listen to the +/// `source` stream and forward all events, date and errors, to the returned +/// stream. When the `source` stream closes, the `yield*` is done, +/// and the `optionalMap` function body ends too. This closes the returned +/// stream. +/// If a `convert` *is* supplied, the function instead listens on the source +/// stream and enters an `await for` loop which +/// repeatedly waits for the next data event. +/// On a data event, it calls `convert` with the value and emits the result +/// on the returned stream. +/// If no error events are emitted by the `source` stream, +/// the loop ends when the `source` stream does, +/// then the `optionalMap` function body completes, +/// which closes the returned stream. +/// On an error event from the `source` stream, +/// the `await for` that error is (re-)thrown which breaks the loop. +/// The error then reaches the end of the `optionalMap` function body, +/// since it's not caught. +/// That makes the error be emitted on the returned stream, which then closes. +/// +/// The `Stream` class also provides functionality which allows you to +/// manually listen for events from a stream, or to convert a stream +/// into another stream or into a future. +/// +/// The [forEach] function corresponds to the `await for` loop, +/// just as [Iterable.forEach] corresponds to a normal `for`/`in` loop. +/// Like the loop, it will call a function for each data event and break on an +/// error. +/// +/// The more low-level [listen] method is what every other method is based on. +/// You call `listen` on a stream to tell it that you want to receive +/// events, and to registers the callbacks which will receive those events. +/// When you call `listen`, you receive a [StreamSubscription] object /// which is the active object providing the events, /// and which can be used to stop listening again, /// or to temporarily pause events from the subscription. @@ -33,19 +85,21 @@ typedef void _TimerCallback(); /// It doesn't start generating events until it has a listener, /// and it stops sending events when the listener is unsubscribed, /// even if the source of events could still provide more. +/// The stream created by an `async*` function is a single-subscription stream, +/// but each call to the function creates a new such stream. /// /// Listening twice on a single-subscription stream is not allowed, even after /// the first subscription has been canceled. /// /// Single-subscription streams are generally used for streaming chunks of -/// larger contiguous data like file I/O. +/// larger contiguous data, like file I/O. /// /// *A broadcast stream* allows any number of listeners, and it fires /// its events when they are ready, whether there are listeners or not. /// /// Broadcast streams are used for independent events/observers. /// -/// If several listeners want to listen to a single subscription stream, +/// If several listeners want to listen to a single-subscription stream, /// use [asBroadcastStream] to create a broadcast stream on top of the /// non-broadcast stream. /// @@ -77,7 +131,8 @@ typedef void _TimerCallback(); /// /// The default implementation of [isBroadcast] returns false. /// A broadcast stream inheriting from [Stream] must override [isBroadcast] -/// to return `true`. +/// to return `true` if it wants to signal that it behaves like a broadcast +/// stream. abstract class Stream { Stream(); @@ -85,6 +140,7 @@ abstract class Stream { /// /// If mixins become compatible with const constructors, we may use a /// stream mixin instead of extending Stream from a const class. + /// (They now are compatible. We still consider, but it's not urgent.) const Stream._internal(); /// Creates an empty broadcast stream. @@ -93,10 +149,10 @@ abstract class Stream { /// when it's listened to. const factory Stream.empty() = _EmptyStream; - /// Creates a stream which emits a single data event before completing. + /// Creates a stream which emits a single data event before closing. /// /// This stream emits a single data event of [value] - /// and then completes with a done event. + /// and then closes with a done event. /// /// Example: /// ```dart