openplanning

Hướng dẫn và ví dụ Dart Future

  1. Tổng quan
  2. Future - Các ví dụ cơ bản
  3. Ví dụ async-await
  4. Xử lý ngoại lệ trong async-await
  5. Constructors
  6. Methods
  7. Future(..)
  8. Future.microtask(..)
  9. Future.sync(..)
  10. Future.delayed(..)
  11. Future.value(..)
  12. Future.error(..)
  13. wait<T>(..) *
  14. any<T>(..) *
  15. forEach<T>(..) *
  16. doWhile(..) *
  17. asStream()
  18. timeout(..)
  19. whenComplete(..)
  20. catchError(..)
  21. then<R>(..)
New fragment 1412546

1. Tổng quan

  • dart:async
abstract class Future<T>

2. Future - Các ví dụ cơ bản

Để dễ hiểu về mục đích sử dụng của lớp Future chúng ta nhìn vào 2 phương thức dưới đây của lớp File, cả hai phương thức đều được sử dụng để đọc nội dung văn bản của file hiện tại.
File class
// (1) - Synchronous method.
String readAsStringSync({Encoding encoding = utf8});

// (2) - Asynchronous method.
Future<String> readAsString({Encoding encoding = utf8})
  • readAsStringSync(): Đọc nội dung văn bản của file một cách đồng bộ, và trả về một String (nội dung văn bản của file).
  • readAsString(): Đọc nội dung văn bản của file một cách không đồng bộ, và trả về một đối tượng Future<String>. Ký hiệu <String> ám chỉ rằng kết quả trả về này có liên quan tới nội dung văn bản của file.
Chúng ta sẽ phân tích sự khác biệt giữa hai phương thức trên:
readAsStringSync()
readAsString()
String readAsStringSync({
  Encoding encoding = utf8,
});
Future<String> readAsString({
  Encoding encoding = utf8,
});
Phương thức này đọc và trả về một String (Nội dung của file).
Phương thức này đọc và trả về một Future<String> (Nội dung của file sẽ nhận được trong tương lai).
Phương thức này không ngay lập tức trả về kết quả, nó cần một khoảng thời gian để đọc toàn bộ nội dung file.
Phương thức này gần như ngay lập tức trả về kết quả là một Future<String>.
Sau khi kết quả được trả về không có gì được thực hiện sau đó.
Sau khi kết quả được trả về, việc đọc file vẫn đang diễn ra một cách âm thầm ở nền và không ảnh hướng tới tiến trình hiện tại.
-
Bạn nói với đối tượng Future rằng, nếu việc đọc file thành công hãy gọi hàm successCallback. Ngược lại nếu thất bại nó sẽ gọi hàm errorCallback.
future  
  // Successfully
  .then(successCallback)  
  // Error
  .catchError(errorCallback)
Bạn sẽ viết code cho các hàm successCallbackerrorCallback để xử lý cho 2 tình huống nói trên.
Sử dụng hàm readAsStringSync() giống như việc bạn đi mua đồ ăn và đứng chờ cho tới khi đầu bếp hoàn thành việc nấu nướng.
Sử dụng hàm readAsString() giống như việc bạn đi mua đồ ăn và nhận được ngay lập tức một hoá đơn với lời hứa bạn sẽ có đồ ăn trong tương lai. Bạn có thể rời đi để làm việc gì đó, khi đầu bếp hoàn thanh việc nấu nướng, đồ ăn sẽ được gửi tới cho bạn.
Để đơn giản hãy xem xét một ví dụ. Đọc một file theo 2 phương thức nói trên.
bookshelf.xml
<?xml version="1.0"?>
<bookshelf>
    <price>132.00</price>
</bookshelf>
readAsStringSync()
Sử dụng phương thức readAsStringSync():
file_readAsStringSync_ex1.dart
import 'dart:io';

void main() {
  var file = File('./input/bookshelf.xml');
  print('(1) - Before calling file.readAsStringSync()'); // (1)

  var value = file.readAsStringSync();
  print('(2) - File content:\n $value'); // (2)

  print('(3) - End of program code'); // (3)
}
Output:
(1) - Before calling file.readAsStringSync()
(2) - File content:
 <?xml version="1.0"?>
<bookshelf>
    <price>132.00</price>
</bookshelf>
(3) - End of program code
  • Nhìn vào đầu ra của ví dụ trên, chúng ta thấy rằng các thông điệp (1), (2) và (3) đã được in ra một cách lần lượt.
  • Chương trình sẽ tạm dừng tại dòng mã file.readAsStringSync() cho tới khi hoàn thành việc đọc nội dung của file và trả về nội dung đó.
readAsString()
Tiếp theo, hãy xem một ví dụ sử dụng phương thức readAsString():
file_readAsString_ex1.dart
import 'dart:async';
import 'dart:io';

void main() {
  var file = File('./input/bookshelf.xml');

  print('(1) - Before calling file.readAsString()'); // (1)
  Future<String> future = file.readAsString(); // (***)

  successCallback(String value) {
    print('(2) File Content:\n $value'); // (2) (*)
  }

  errorCallback(dynamic e, StackTrace st) {
    print('(2) Error:\n $e'); // (2) (**)
  }

  future // (****)
      .then(successCallback) // If the file is read successfully.
      .catchError(errorCallback); // If an error occurs.

  print('(3) - End of program code'); // (3)
}
Output:
(1) - Before calling file.readAsString()
(3) - End of program code
(2) File Content:
 <?xml version="1.0"?>
<bookshelf>
    <price>132.00</price>
</bookshelf>
  • Các thông điệp đã được in ra theo thứ tự (1), (3), (2)
  • Phương thức file.readAsString() đã ngay lập tức trả về đối tượng Future<String>, mặc dù việc đọc nội dung file chưa hoàn thành. Sau đó, chương trình tiếp tục thực thi các dòng mã tiếp theo.
  • Future<String> chỉ là một đối tượng logic, nó không chứa nội dung của file. Sau khi đối tượng này được trả về, việc đọc file vẫn được thực hiện một cách âm thầm dưới nền của ứng dụng. Khi việc đọc file hoàn thành hàm successCallback sẽ được gọi. Nếu có lỗi xẩy ra hàm errorCallback sẽ được gọi.
  • Dòng mã (****) là cách lắng nghe các sự kiện xẩy ra với đối tượng Future<String>.
Kết luận:
Một Future<T> được sử dụng để đại diện cho một giá trị (hoặc một lỗi) tiềm năng sẽ xuất hiện trong tương lai. Bạn cần đăng ký để lắng nghe các sự kiện xẩy ra với đối tượng này và xử lý chúng.

3. Ví dụ async-await

Đây là cách gọi phương thức theo cách thông thường:
void main() {
  var file = File('./input/bookshelf.xml'); 
 
  Future<String> future = file.readAsString(); // <---- (**) 
}
Trong ví dụ này, từ khoá "await" yêu cầu chương trình đợi cho tới khi phương thức đọc xong nội dung của file và trả về String thay vì trả về Future<String>:
import 'dart:io';

Future<void> main() async { // <------------------------
  var file = File('./input/bookshelf.xml');

  print('(1) - Before calling file.readAsString()');
  String content = await file.readAsString(); // <------ (**)

  print('(2) - File content:\n $content');
  print('(3) - End of program code');
}
  • Nếu từ khoá await xuất hiện trong một hàm (hoặc phương thức) thì hàm (hoặc phương thức) này phải được đánh dấu với từ khoá async và trả về kiểu Future.
Trường hợp đặc biệt áp dụng cho "void":
  • Future<void>void được coi là như nhau.
Kết quả của ví dụ trên:
(1) - Before calling file.readAsString()
(2) - File content:
 <?xml version="1.0"?>
<bookshelf>
    <price>132.00</price>
</bookshelf>
(3) - End of program code

4. Xử lý ngoại lệ trong async-await

Đôi khi lỗi có thể xẩy ra trong chương trình, chẳng hạn khi bạn cố gắng đọc nội dung của một file không tồn tại.
file_readAsString_ex3a.dart
import 'dart:io';

Future<void> main() async {
  var file = File('./input/XXX.xml'); // a file not exists!!

  print('(1) - Before calling file.readAsString()'); 
  try {
    String content = await file.readAsString(); // <------ (**)
    print('(2) - File content:\n $content');
  } on Exception catch (msg, st) {
    // (String msg, StackTrace st)
    print('(2) - Error: $msg');
  }

  print('(3) - End of program code');
}
Output:
(1) - Before calling file.readAsString()
(2) - Error: PathNotFoundException: Cannot open file, path = './input/XXX.xml' (OS Error: No such file or directory, errno = 2)
(3) - End of program code
Việc xử lý lỗi như ví dụ trên tương tự với ví dụ dưới đây:
file_readAsString_ex3b.dart
import 'dart:async';
import 'dart:io';

void main() {
  var file = File('./input/XXX.xml'); // a file not exists!!

  print('(1) - Before calling file.readAsString()'); // (1)
  Future<String> future = file.readAsString(); // (***)

  successCallback(String value) {
    print('(2) File Content:\n $value'); // (2) (*)
  }

  errorCallback(dynamic e, StackTrace st) {
    print('(2) Error: $e'); // (2) (**)
  }

  future // (****)
      .then(successCallback) // If the file is read successfully.
      .catchError(errorCallback); // If an error occurs.

  print('(3) - End of program code'); // (3)
}

5. Constructors

Các constructor của lớp Future<T>:
Future(FutureOr<T> computation())
Future.microtask(FutureOr<T> computation())  
Future.sync(FutureOr<T> computation())
Future.value([FutureOr<T>? value])
Future.error(Object error, [StackTrace? stackTrace])  
Future.delayed(Duration duration, [FutureOr<T> computation()?])

6. Methods

Các phương thức:
static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
                                           {bool eagerError = false, void cleanUp(T successValue)?})

static Future<T> any<T>(Iterable<Future<T>> futures)  
static Future forEach<T>(Iterable<T> elements, FutureOr action(T element))  
static Future doWhile(FutureOr<bool> action())  

Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError})
Future<T> catchError(Function onError, {bool test(Object error)?})
Future<T> whenComplete(FutureOr<void> action())  
Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()?})

Stream<T> asStream()

7. Future(..)

factory Future(FutureOr<T> computation())
Từ một hàm computation() dùng để thực hiện một nhiệm vụ nào đó, sử dụng constructor này để tạo ra một đối tượng Future<T> bao bọc hàm computation() và thực thi hàm này với sự hỗ trợ của phương thức tĩnh Timer.run().
  • computation: Là một hàm thực hiện một nhiệm vụ nào đó. Kiểu trả về là Future<T> hoặc <T>.
Để hiểu thêm hãy xem mã nguồn của constructor này:
factory Future(FutureOr<T> computation()) {
  _Future<T> result = new _Future<T>();
  Timer.run(() {
    try {
      result._complete(computation());
    } catch (e, s) {
      _completeWithErrorCallback(result, e, s);
    }
  });
  return result;
}
Ví dụ:
future_ex1.dart
import 'dart:async';
import 'dart:math';

void main() {
  print("Start of code");

  // A function returns a String:
  // (A task need to do)
  String computation() {
    print(
        'Querying user from Database... (it may take a few milliseconds)');

    // A random bool value
    var testSuccess = Random().nextBool(); // true or false.

    if (testSuccess) {
      return 'Full Name: Tom, Country: American';
    } else {
      throw Exception('Error while connecting with database');
    }
  }

  var future = Future<String>(computation);

  future //
      .then((String value) => print(' - SUCCESS: $value'))
      .onError((error, stackTrace) => print(' - ERROR: $error'));

  print('End of code');
}
Output:
Start of code
End of code
Querying user from Database... (it may take a few milliseconds)
 - SUCCESS: Full Name: Tom, Country: American
Hoặc:
Start of code
End of code
Querying user from Database... (it may take a few milliseconds)
 - ERROR: Exception: Error while connecting with database
Ví dụ:
Hàm sayHello() là một hàm đồng bộ, gọi hàm này theo cách thông thường.
Gọi một hàm đồng bộ một cách không đồng bộ:
future_ex2a.dart
void sayHello() {
  for (int i = 0; i < 3; i++) {
    print("Hello $i");
  }
}

void main() {
  print("Start of code");

  sayHello();

  print('End of code');
}
future_ex2b.dart
void sayHello() {
  for (int i = 0; i < 3; i++) {
    print("Hello $i");
  }
}

void main() {
  print("Start of code");

  Future(sayHello);

  print('End of code');
}
Output:
Start of code
Hello 0
Hello 1
Hello 2
End of code
Output:
Start of code
End of code
Hello 0
Hello 1
Hello 2
Ví dụ:
Hàm download() là một hàm đồng bộ, gọi hàm này theo cách thông thường.
Gọi một hàm đồng bộ một cách không đồng bộ:
future_ex3a.dart
String download() {
  print("> Downloading.");
  print("> Downloading..");
  print("> Downloading...");
  return "100 MB";
}

void main() {
  print("Start of code");

  String c = download();
  print("> File Content: $c");

  print('End of code');
}
future_ex3b.dart
import 'dart:async';

String download() {
  print("> Downloading.");
  print("> Downloading..");
  print("> Downloading...");
  return "100 MB";
}

void main() {
  print("Start of code");

  Future<String> f = Future(download);
  f.then(
    (c) => print("> File Content: $c"),
  );

  print('End of code');
}
Output:
Start of code
> Downloading.
> Downloading..
> Downloading...
> File Content: 100 MB
End of code
Output:
Start of code
End of code
> Downloading.
> Downloading..
> Downloading...
> File Content: 100 MB

8. Future.microtask(..)

factory Future.microtask(FutureOr<T> computation())
Từ một hàm computation() dùng để thực hiện một nhiệm vụ nào đó, sử dụng constructor này để tạo ra một đối tượng Future<T> bao bọc hàm computation() và thực thi hàm này với sự hỗ trợ của hàm scheduleMicrotask().
  • computation: Là một hàm thực hiện một nhiệm vụ nào đó. Kiểu trả về là Future<T> hoặc <T>.
Để hiểu thêm hãy xem mã nguồn của constructor này:
factory Future.microtask(FutureOr<T> computation()) {
  _Future<T> result = new _Future<T>();
  scheduleMicrotask(() {
    try {
      result._complete(computation());
    } catch (e, s) {
      _completeWithErrorCallback(result, e, s);
    }
  });
  return result;
}
Cách sử dụng constructor Future.microtask() tương tự với constructor Future(). Điều khác biệt là các tác vụ nhỏ luôn được ưu tiên thực thi trước các tác vụ khác nếu có thể. Hay nói cách khác Future.microtask() được ưu tiên thực thi trước Future(). Hãy xem ví dụ dưới đây:
future_vs_microtask_ex1.dart
import 'dart:async';

void main() {
  print('Start...');
  Future(() => print('Future() 1'));
  Future(() => print('Future() 2'));
  // Future.microtask() will be executed before Future().
  Future.microtask(() => print('Future.microtask() 1'));
  Future.microtask(() => print('Future.microtask() 2'));
  print('End!');
}
Output:
Start...
End!
Future.microtask() 1
Future.microtask() 2
Future() 1
Future() 2

9. Future.sync(..)

factory Future.sync(FutureOr<T> computation())
Tạo ra một đối tượng Future<T> chứa kết quả của việc gọi ngay lập tức hàm computation().
  • computation: Là một hàm thực hiện một nhiệm vụ nào đó. Kiểu trả về là Future<T> hoặc <T>.
Mã nguồn của constructor này:
factory Future.sync(FutureOr<T> computation()) {
  try {
    var result = computation();
    if (result is Future<T>) {
      return result;
    } else {
      return new _Future<T>.value(result as dynamic);
    }
  } catch (error, stackTrace) {
    ...
  }
}

10. Future.delayed(..)

factory Future.delayed(Duration duration, [FutureOr<T> computation()?])
Tạo ra một đối tượng Future<T> chứa kết quả của việc gọi hàm computation() với độ trễ được chỉ định.
  • duration: Khoảng thời gian trễ.
  • computation: Là một hàm thực hiện một nhiệm vụ nào đó. Kiểu trả về là Future<T> hoặc <T>.
Ví dụ:
future_delayed_ex1.dart
import 'dart:async';

Future<void> main() async { await Future.delayed(Duration(seconds: 10));
  print('(1) Now is: ${DateTime.now()}');

  String computation() {
    print('\n');
    print('(2) Now is: ${DateTime.now()}');
    print('(2) Querying user...');
    return 'Full Name: Tom Cat, Country: American';
  }

  // Delay 3 seconds before call "computation" function:
  Future<String> future = Future.delayed(Duration(seconds: 3), computation);

  future.then((value) => print('QUERY RESULT: $value'));

  print('(3) Now is: ${DateTime.now()}');
}
Output:
(1) Now is: 2024-06-20 13:38:31.695486
(3) Now is: 2024-06-20 13:38:31.700179


(2) Now is: 2024-06-20 13:38:34.702022
(2) Querying user...
QUERY RESULT: Full Name: Tom Cat, Country: American

11. Future.value(..)

factory Future.value([FutureOr<T>? value])
Tạo một đối tượng Future<T> dựa trên tham số value được cung cấp. Có 2 tình huống xẩy ra:
  • Nếu value là một đối tượng Future, constructor này sẽ chờ cho tới khi tương lai này hoàn thành để có được giá trị value2. Sau đó tạo ra đối tượng Future hoàn thành với giá trị value2.
  • Nếu value không phải là một đối tượng Future. Contructor này sẽ tạo ra đối tượng Future hoàn thành với giá trị value.
Mã nguồn của constructor này:
factory Future.value([FutureOr<T>? value]) {
  return new _Future<T>.immediate(value == null ? value as T : value);
}
Ví dụ:
future_value_ex1.dart
import 'dart:async';

void main() {
  // Delay 3 seconds before completing with the value 'Tom Cat':
  Future<String> value = Future.delayed(Duration(seconds: 3), () {
    print('(1) Completed! Now is: ${DateTime.now()}');
    return 'Tom Cat';
  });

  print('(2) Now is: ${DateTime.now()}');
  var future = Future.value(value);

  future.then((value2) {
    print('(3) Now is: ${DateTime.now()}, value2: $value2');
  });

  print('(4) Done!');
}
Output:
(2) Now is: 2024-06-20 13:48:26.798297
(4) Done!
(1) Completed! Now is: 2024-06-20 13:48:29.800181
(3) Now is: 2024-06-20 13:48:29.802857, value2: Tom Cat

12. Future.error(..)

factory Future.error(Object error, [StackTrace? stackTrace])
Tạo một đối tượng Future hoàn thành với một lỗi.
Ví dụ:
future_error_ex1.dart
import 'dart:async';

void main() {
  Future<Null> future = Future.error('Invalid number');
  
  future //
      .then((value) {
        print('Never happened!');
      }) 
      .onError((error, stackTrace) {
        print('Error: $error');
      });
}
Output:
Error: Invalid number

13. wait<T>(..) *

static Future<List<T>> wait<T>(
  Iterable<Future<T>> futures, {
  bool eagerError = false,
  void cleanUp(T successValue)?,
});
Phương thức tĩnh này kết hợp các Future được chỉ định để tạo ra một đối tượng Future mới. Future mới hoàn thành khi tất cả các Future đã cho hoàn thành thành công hoặc lỗi xẩy ra.
futures
Các đối tượng Future được chỉ định.
eagerError
Nếu giá trị của tham số này là true, nghĩa là nếu tìm thấy một Future đầu tiên bị lỗi, Future được trả về cũng hoàn thành ngay lập tức với một lỗi.
cleanUp
Hàm này chỉ được gọi trong trường hợp lỗi xẩy ra. Mục đích của nó là giải phóng tài nguyên cho mỗi giá trị thành công (successValue), điều này cần thiết vì đối tượng Future được trả về không cung cấp quyền truy cập vào các giá trị này.
Ví dụ:
Future_wait_ex1.dart
import 'dart:async';
void main() {
  print('(1): Start... ${DateTime.now()}');

  var future1 = Future.delayed(Duration(seconds: 5), () {
    print('(2): future 1 completed! ${DateTime.now()}');
    return 'Tom';
  });
  var future2 = Future.delayed(Duration(seconds: 10), () {
    print('(3): future 2 completed! ${DateTime.now()}');
    return 'Jerry';
  });

  // Iterable<String>
  var futures = [future1, future2]; // A List

  Future<List<String>> future = Future.wait(futures);

  future.then((List<String> list) {
    print('(4): List: ${list}');
  });
  print('(5): End ${DateTime.now()}');
}
Output:
(1): Start... 2021-12-20 18:42:22.491595
(5): End 2021-12-20 18:42:22.502372
(2): future 1 completed! 2021-12-20 18:42:27.505567
(3): future 2 completed! 2021-12-20 18:42:32.502612
(4): List: [Tom, Jerry]

14. any<T>(..) *

static Future<T> any<T>(Iterable<Future<T>> futures)
Phương thức tĩnh này trả về Future đầu tiên hoàn thành (thành công hoặc lỗi) trong các Future đã cho.
Ví dụ:
Future_any_ex1.dart
import 'dart:async';

void main() {
  print('(1): Start... ${DateTime.now()}');

  var future1 = Future.delayed(Duration(seconds: 1), () {
    print('(2): future 1 completed! ${DateTime.now()}');
    return 'Tom';
  });
  var future2 = Future.delayed(Duration(seconds: 2), () {
    print('(3): future 2 completed! ${DateTime.now()}');
    return 'Jerry';
  });
  var future3 = Future.delayed(Duration(seconds: 3), () {
    throw Exception('(4): Error in future3');
  });

  // Iterable<String>
  var futures = [future1, future2, future3]; // A List

  Future<String> future = Future.any(futures);

  future.then((String value) {
    print('(5): Value: ${value}');
  });
  print('(6): End ${DateTime.now()}');
}
Output:
(1): Start... 2021-12-20 23:55:13.081201
(6): End 2021-12-20 23:55:13.091670
(2): future 1 completed! 2021-12-20 23:55:14.093595
(5): Value: Tom
(3): future 2 completed! 2021-12-20 23:55:15.093739

15. forEach<T>(..) *

static Future forEach<T>(Iterable<T> elements, FutureOr action(T element))

16. doWhile(..) *

static Future doWhile(FutureOr<bool> action())

17. asStream()

Stream<T> asStream()

18. timeout(..)

Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()?})

19. whenComplete(..)

Future<T> whenComplete(FutureOr<void> action())

20. catchError(..)

Future<T> catchError(Function onError, {bool test(Object error)?})
  • Dart Exceptions

21. then<R>(..)

Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError})