[doc, io] Document the error behavior for File.openRead/file.openWrite

I recently fixed a bug by adding a `sink.done.ignore()` into library code: https://dart-review.googlesource.com/c/sdk/+/351380

My thinking now is that is a bad idea because it allows errors to pass silently if the sink is not closed or flushed (and the results are awaited!). Instead, we should document this as a general pattern for sinks.

...but that isn't satisfying either. `sink.done.ignore()` really means "I promise that I will handle errors elsewhere" but there is no actual enforcement of that.

Bug:https://github.com/dart-lang/sdk/issues/54707
Change-Id: I92feb43b1b2c57933c2343f4b6d354792cd13d72
CoreLibraryReviewExempt: dart io documentation-only
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352442
Commit-Queue: Brian Quinlan <bquinlan@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
diff --git a/sdk/lib/io/file.dart b/sdk/lib/io/file.dart
index 6993986..9b47226 100644
--- a/sdk/lib/io/file.dart
+++ b/sdk/lib/io/file.dart
@@ -144,13 +144,14 @@
 /// ```dart
 /// import 'dart:io';
 ///
-/// void main() {
+/// void main() async {
 ///   var file = File('file.txt');
 ///   var sink = file.openWrite();
 ///   sink.write('FILE ACCESSED ${DateTime.now()}\n');
+///   await sink.flush();
 ///
 ///   // Close the IOSink to free system resources.
-///   sink.close();
+///   await sink.close();
 /// }
 /// ```
 /// ## The use of asynchronous methods
@@ -497,8 +498,21 @@
   /// to the pipe when it is opened, then [Stream.listen] will wait until
   /// a writer opens the pipe.
   ///
-  /// Any errors opening or reading the file will appear as error events in
-  /// the returned [Stream].
+  /// An error opening or reading the file will appear as a
+  /// [FileSystemException] error event on the returned [Stream], after which
+  /// the [Stream] is closed. For example:
+  ///
+  /// ```dart
+  /// // This example will print the "Error reading file" message and the
+  /// // `await for` loop will complete normally, without seeing any data
+  /// // events.
+  /// final stream = File('does-not-exist')
+  ///     .openRead()
+  ///     .handleError((e) => print('Error reading file: $e'));
+  /// await for (final data in stream) {
+  ///   print(data);
+  /// }
+  /// ```
   Stream<List<int>> openRead([int? start, int? end]);
 
   /// Creates a new independent [IOSink] for the file.
@@ -520,6 +534,38 @@
   /// The returned [IOSink] does not transform newline characters (`"\n"`) to
   /// the platform's conventional line ending (e.g. `"\r\n"` on Windows). Write
   /// a [Platform.lineTerminator] if a platform-specific line ending is needed.
+  ///
+  /// If an error occurs while opening or writing to the file, the [IOSink.done]
+  /// [IOSink.flush], and [IOSink.close] futures will all complete with a
+  /// [FileSystemException]. You must handle errors from the [IOSink.done]
+  /// future or the error will be uncaught.
+  ///
+  /// For example, [FutureExtensions.ignore] the [IOSink.done] error and
+  /// remember to `await` the [IOSink.flush] and [IOSink.close] calls within a
+  /// `try`/`catch`:
+  ///
+  /// ```dart
+  /// final sink = File('/tmp').openWrite(); // Can't write to /tmp
+  /// sink.done.ignore();
+  /// sink.write("This is a test");
+  /// try {
+  ///   // If one of these isn't awaited, then errors will pass silently!
+  ///   await sink.flush();
+  ///   await sink.close();
+  /// } on FileSystemException catch (e) {
+  ///   print('Error writing file: $e');
+  /// }
+  /// ```
+  ///
+  /// To handle errors asynchronously outside of the context of [IOSink.flush]
+  /// and [IOSink.close], you can [Future.catchError] the [IOSink.done].
+  ///
+  /// ```dart
+  /// final sink = File('/tmp').openWrite(); // Can't write to /tmp
+  /// sink.done.catchError((e) {
+  ///  // Handle the error.
+  /// });
+  /// ```
   IOSink openWrite({FileMode mode = FileMode.write, Encoding encoding = utf8});
 
   /// Reads the entire file contents as a list of bytes.
@@ -1104,7 +1150,8 @@
 /// ```dart
 /// final pipe = await Pipe.create();
 /// pipe.write.add("Hello World!".codeUnits);
-/// pipe.write.close();
+/// await pipe.write.flush();
+/// await pipe.write.close();
 /// ```
 abstract interface class WritePipe implements IOSink {}
 
@@ -1124,7 +1171,8 @@
 ///     <ResourceHandle>[ResourceHandle.fromReadPipe(pipe.read)])
 /// ], 'Hello'.codeUnits);
 /// pipe.write.add('Hello over pipe!'.codeUnits);
-/// pipe.write.close();
+/// await pipe.write.flush();
+/// await pipe.write.close();
 /// ```
 abstract interface class Pipe {
   /// The read end of the [Pipe].