[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].