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:
Đợ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.
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ộ.