Асинхронные функции и операторы async и await

Последнее обновление: 09.04.2023

Операторы 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

Необязательно вызов асинхронных операций выносить в отдельные асинхронные функции. Можно сразу сделать асинхронной функцию 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
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850