Операторы async и await облегчают написание асинхронного кода. Применение async-await позволяет уйти от прямого использования Future API, в частности, его методов then и catchError.
Сначала рассмотрим, как бы написали код с помощью Future API без async и await:
void doWork() { print("Начало функции doWork"); Future<String> messageFuture = getMessage(); messageFuture.then((message){ print("Получено сообщение: $message"); }); print("Завершение функции doWork"); } Future<String> getMessage() { // имитация долгой работы с помощью задержки в 3 секунды return Future.delayed(Duration(seconds: 3), () => "Hello Dart"); } void main () { doWork(); print("Выполнение функции main"); }
В данном случае определена функция getMessage()
, которая должна получать некоторое сообщение. Для имитации долговременной работы устанавливаем в ней задержку в 3 секунды. Но в реальности
это может быть функция, которая выполняет сетевые запросы, обращения к базе данных, чтение-запись файлов и другую тяжеловесную работу, которая может занять
долгое время.
Допустим, в функции doWork()
мы вызываем функцию getMessage, которая возращает объект Future. Через его метод then
получаем нужное сообщение и выводим его на консоль. В функции main вызываем doWork.
Консольный вывод программы:
Начало функции doWork Завершение функции doWork Выполнение функции main Получено сообщение: Hello Dart
Обратите внимание, что функция doWork завершает свое выполнение до завершения Future.
Теперь перепишем тот же код с использованием async-await:
Future<void> doWork() async { print("Начало функции doWork"); String message = await getMessage(); print("Получено сообщение: $message"); print("Завершение функции doWork"); } Future<String> getMessage() { // имитация долгой работы с помощью задержки в 3 секунды return Future.delayed(Duration(seconds: 3), () => "Hello Dart"); } void main () { doWork(); print("Выполнение функции main"); }
Функция getMessage, которая выполняет некую долговременную работу (в данном случае получает сообщение), остается такой же. Основые изменения касаются функции doWork, которая обращается к getMessage для получения сообщения. Теперь она определена как асинхронная. Для этого после списка параметров функции указывается оператор async:
Future<void> doWork() async {
Асинхронная функция - это такая функция, которая содержит как минимум одну асинхронную операцию (хотя также вместе с этим может содержать выполнять и синхронные операции). Асинхронная функция выглядит как синхронная за тем исключением, что она использует операторы async и await.
Асинхронная функция выполняет синхронно весь код, который идет до первого вызова выражения await. В данном случае синхронно выполняется следующая строка:
print("Начало функции doWork");
Выражение await представляет асинхронную операцию. Асинхронная операция - это такая операция, которая не блокирует выполнение других операций и позволяет им выполняться до своего завершения. В данном случае это:
String message = await getMessage();
Выражение await обычно возвращает объект Future. Если возвращается объект другого типа, то он автоматически обертывается во Future. И этот объект, который хранится во Future, мы можем получить простой операцией присваивания. Так, функция getMessage() возвращает Future<String>, то есть Future хранит некоторую строку. И путем операции присвоения мы можем передать эту строку в переменную message. Дальше мы можем делать с этой строкой, что хотим, например, вывести на консоль.
Выражение await приостанавливает выполнение, пока нужный объект(в данном случае строка) не будет доступен. А асинхронная функция getMessage возращает объект Future, который начинает свое выполнение.
И уже после того, как из выражения await мы получили строку, функция doWork продолжает свое выполнение и переходит к выполнению оставшегося кода:
print("Получено сообщение: $message"); print("Завершение функции doWork");
В итоге в данном случае консольный вывод будет выглядеть следующим образом:
Начало функции doWork Выполнение функции main Получено сообщение: Hello Dart Завершение функции doWork
Стоит отметить, что если функция использует await, то она обязательно должна быть определена с ключевым словом async. Также стоит отметить, что асинхронная функция может содержать несколько выражений await. В этом случае они выполняется последовательно: когда завершится предыдущее, начинает выполняться последующее выражение await.
Необязательно вызов асинхронных операций выносить в отдельные асинхронные функции. Можно сразу сделать асинхронной функцию main:
Future<String> getMessage() { // имитация долгой работы с помощью задержки в 3 секунды return Future.delayed(Duration(seconds: 3), () => "Hello Dart"); } Future<void> main () async{ print("Начало функции main"); String message = await getMessage(); print("Получено сообщение: $message"); print("Завершение функции main"); }
В определении функции main также можно убрать Future и оставить только void:
void main () async{ print("Начало функции main"); String message = await getMessage(); print("Получено сообщение: $message"); print("Завершение функции main"); }
Если в процессе выполнения долговременной операции произошла ошибка, то объект Future вместо конкретных данных будет содержать информацию об ошибке. Для обработки подобной ситуации достаточно поместить выражение await в блок try..catch:
Future<void> doWork() async { print("Начало функции doWork"); try{ String message = await getMessage(); print("Получено сообщение: $message"); } catch(e){ print("Произошла ошибка: $e"); } print("Завершение функции doWork"); } Future<String> getMessage() { // с помощью оператора throw имитируем возникновение ошибки return Future.delayed(Duration(seconds: 3), () => throw "Сервер не отвечает"); } void main () { doWork(); print("Выполнение функции main"); }
Для имитации ошибки вызываем оператор throw, после которого указываем текст ошибки. В асинхроннной функции в конструкции try..catch мы можем перехватить возникновение ошибки и обработать ее. В итоге консольный вывод будет выглядеть следующим образом:
Начало функции doWork Выполнение функции main Произошла ошибка: Сервер не отвечает Завершение функции doWork