Tự học Flutter 2023

New Dart

Lập trình bất đồng bộ trong Dart

Lập trình bất đồng bộ cho phép chương trình của bạn hoàn thành công việc trong khi chờ hoạt động khác kết thúc. Một số hoạt động bất đồng bộ phổ biến có thể kể đến như: Tìm nạp dữ liệu qua mạng; Ghi vào cơ sở dữ liệu; Đọc dữ liệu từ một tập tin.

Dart hỗ trợ lập trình bất đồng bộ với các class như Future, cùng các từ khóa như await, async.

  • Future là gì?
  • Làm sao để tạo ra Future?
  • Sử dụng Future constructor
  • Sử dụng Completer
  • Sử dụng async
  • Làm việc với Future
  • Sử dụng cơ bản
  • Nối tiếp nhiều phương thức bất đồng bộ
  • Đợi nhiều future cùng lúc
  • Async/await
  • Execution flow with async and await
  • Xử lý lỗi
  • Future là gì?

    Một Future là đại diện cho kết quả của một hoạt động không đồng bộ và có thể có hai trạng thái: chưa hoàn thành hoặc đã hoàn thành.

    
    Future<List<Order>> fetchOrders() { ... }
                            

    Chưa hoàn thành

    Khi bạn gọi một hàm không đồng bộ, nó sẽ trả về một Future chưa hoàn thành. Future đó đang chờ hoạt động không đồng bộ của chức năng kết thúc hoặc đưa ra lỗi.

    Đã hoàn thành

    Nếu hoạt động không đồng bộ thành công, Future sẽ hoàn thành với một giá trị. Nếu không, nó hoàn thành với một lỗi.

    Hoàn thành với kết quả

    Một Future loại Future<T> hoàn thành với một giá trị của loại T. Ví dụ: một Future với loại Future<String> tạo ra một giá trị String. Nếu một Future không tạo ra giá trị có thể sử dụng, thì loại Future đó là Future<void>.

    Hoàn thành với lỗi

    Nếu hoạt động không đồng bộ được thực hiện bởi chức năng không thành công vì bất kỳ lý do gì, tương lai sẽ hoàn thành với một lỗi.

    Làm sao để tạo ra Future?

    Sử dụng Future constructor

    
    Future<int> cubed(int a) {
      return Future(() => a * a * a);
    }
                            
    
    Future<int> cubed(int a) {
      return Future.value(a * a * a);
    }
                            

    Sử dụng Completer

    
    import 'dart:async';
    
    Future<int> cubed(int a) async {
      final completer = Completer();
      if (a < 0) {
        completer.completeError(ArgumentError("'a' must be positive."));
      } else {
        completer.complete(a * a * a);
      }
      return completer.future;
    }
                            

    Sử dụng async

    Bạn tạm thời bỏ qua cách này, quay trở lại sau khi đã đọc xong các phần tiếp theo. Trong các phần tiếp theo sẽ nói về async, lúc đó bạn sẽ hiểu và quay lại đây.

    
    Future<int> cubed(int a) async {
      return a * a * a;
    }
                            
    Nói chung, hàm bất đồng bộ (được gán từ khóa async) sẽ tự động trả về kiểu Future, nên bạn chỉ cần viết như trên là được.

    Làm việc với Future

    Sử dụng cơ bản

    Bạn có thể sử dụng then() để lên lịch đoạn mã cần chạy khi một future hoàn thành (đăng ký callback). Ví dụ, HttpRequest.getString() trả về một Future, vì HTTP request có thể mất một thời gian mới hoàn thành. Sử dụng then() cho phép bạn chạy một đoạn code khi mà Future đã hoàn thành và kết quả của http request đã có.

    
    HttpRequest.getString(url).then((String result) {
      print(result);
    });
                          

    Sử dụng catchError() để xử lý bất kỳ lỗi hoặc ngoại lệ nào được nép ra bởi Future.

    
    HttpRequest.getString(url).then((String result) {
      print(result);
    }).catchError {
      // Handle or ignore the error.
    });
                          

    Nối tiếp nhiều phương thức bất đồng bộ

    Phương thức then() trả về một Future, cung cấp một cách hữu ích để chạy nhiều hàm không đồng bộ theo một thứ tự nhất định. If the callback registered with then() returns a Future, then() returns a Future that will complete with the same result as the Future returned from the callback. If the callback returns a value of any other type, then() creates a new Future that completes with the value.

    
    Future result = costlyQuery(url);
    result
        .then((value) => expensiveWork(value))
        .then((_) => lengthyComputation())
        .then((_) => print('Done!'))
        .catchError((exception) {
      /* Handle exception... */
    });
                          

    Ở ví dụ trên, các phương thức chạy theo thứ tự sau:

  • 1. costlyQuery()
  • 2. expensiveWork()
  • 3. lengthyComputation()
  • Đợi nhiều future cùng lúc

    Đôi khi thuật toán của bạn cần gọi nhiều hàm không đồng bộ và đợi tất cả chúng hoàn thành trước khi tiếp tục. Sử dụng phương thức tĩnh Future.wait() để quản lý nhiều Futures và đợi chúng hoàn thành:

    
    Future<void> deleteLotsOfFiles() async =>  ...
    Future<void> copyLotsOfFiles() async =>  ...
    Future<void> checksumLotsOfOtherFiles() async =>  ...
    
    await Future.wait([
      deleteLotsOfFiles(),
      copyLotsOfFiles(),
      checksumLotsOfOtherFiles(),
    ]);
    print('Done with all the long steps!');
                          

    Async and await

    Bây giờ bạn đã biết cách sử dụng Future, bạn biết cách dùng hàm then() để đăng ký một callback để thực hiện code sau khi Future hoàn thành. Nhưng vẫn còn cách khác để làm cho việc này dễ dàng hơn, mà chính xác là về mặt cú pháp, dễ viết, dễ đọc hơn, nhìn như là code đồng bộ. Đây là lúc bạn cần biết về await.

  • Từ khóa await dùng để đợi hàm đồng bộ hoàn thành công việc, nó chỉ được sử dụng bên trong hàm bất đồng bộ
  • Để định nghĩa một làm là bất đồng bộ, thêm từ khóa async vào trước thân hàm
  • Một hàm bất đồng bộ phải trả về kiểu dữ liệu là Future<T> hoặc Future<void>/void
  • Lấy ví dụ. Thay vì sử dụng then() để xử lý orders trả về, ta sẽ dùng await để đợi kết quả. Và vì để dùng được await ta phải khai báo hàm main thành hàm bất đồng bộ bằng cách thêm từ khóa async vào trước thân hàm.

    
    Future<List<Order>> fetchOrders() { ... }
    
    void main() async {
      var orders = await fetchOrders();
      print(orders)
    }
                          

    Còn nhớ ví dụ về nối tiếp nhiều phương thức bất đồng bộ? Đoạn code đó có thể viết lại với async/await như sau:

    
    try {
      final value = await costlyQuery(url);
      await expensiveWork(value);
      await lengthyComputation();
      print('Done!');
    } catch (e) {
      /* Handle exception... */
    }
                          

    Dễ đọc hơn nhiều đúng không?!

    Execution flow with async and await

    Hàm bất đồng bộ chạy đồng bộ cho đến từ khóa await đầu tiên. Điều này có nghĩa là trong thân hàm async, tất cả mã đồng bộ trước từ khóa await đầu tiên sẽ thực thi ngay lập tức.

    Chạy ví dụ sau để xem cách tiến hành thực thi trong thân hàm không đồng bộ. Bạn nghĩ đầu ra sẽ là gì?

                          
    Future<void> printOrderMessage() async {
      print('Awaiting user order...');
      var order = await fetchUserOrder();
      print('Your order is: $order');
    }
    
    Future<String> fetchUserOrder() {
      // Imagine that this function is more complex and slow.
      return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
    }
    
    void main() async {
      countSeconds(4);
      await printOrderMessage();
    }
    
    // You can ignore this function - it's here to visualize delay time in this example.
    void countSeconds(int s) {
      for (var i = 1; i <= s; i++) {
        Future.delayed(Duration(seconds: i), () => print(i));
      }
    }
    
    

    Xử lý lỗi

    Để xử lý lỗi trong hàm bất đồng bộ, sử dụng try-catch:

    
    try {
      print('Awaiting user order...');
      var order = await fetchUserOrder();
    } catch (err) {
      print('Caught error: $err');
    }
                            
    Trong một hàm không đồng bộ, bạn có thể viết các mệnh đề try-catch giống như cách bạn làm trong mã đồng bộ.