Работа с классом Future

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

Ключевым классом для написания асинхронного кода в Dart является класс Future. Рассмотрим, какие возможности он предоставляет.

Получение значения из Future и метод then

При определении объекта Future он находится в незавершенном состоянии и будет выполнен чуть позже. Но как же поймать тот момент, когда Future уже выполнен и перешел в завершенное состояние? Для этого у Future определен метод then. Этот метод принимает функцию обратного вызова, которая будет срабатывать при завершении Future. В качестве единственного параметра функция принимает полученное из Future значение.

Рассмотрим следующий пример:

void main() {
    Future<String> future = Future.delayed(
     Duration(seconds: 1),
     () {
        print("Выполнение Future");
        return "Hello Future";  // возвращаем строку
    });
     
    future.then((value){
        print("Из Future получено значение: $value");
    });
   
  print("Завершение функции main");
}

Здесь работа с Future происходит в два этапа. Сначала с помощью конструктора Future.delayed определяем задачу, которая будет выполняться:

Future<String> future = Future.delayed(
     Duration(seconds: 1),
     () {
        print("Выполнение Future");
        return "Hello Future";  // возвращаем строку
    });

Эта задача выводит на консоль строку и возвращает строку, поэтому future представляет тип Future<String>. Причем это происходит с задержкой в 1 секунду.

На втором этапе вызываем метод then()

future.then((value){
    print("Из Future получено значение: $value");
});

В метод then() передается функция обратного вызова, которая принимает параметр value - это и будет то значение, которое возвращается при выполнении задачи. Сама функция в then просто выводит это полученное значение на консоль. В итоге мы получим следующий консольный вывод:

Завершение функции main
Выполнение Future
Из Future получено значение: Hello Future

Подобным образом мы можем получать значения других типов - int, double и т.д.

Может возникнуть вопрос, как быть с типом void - в этом случае вместо параметра можно указывать прочерк:

void main() {
	Future<void> future = Future.delayed(
		Duration(seconds: 1), 
		() => print("Hello Future"));		// возвращаемый тип - void
	
	future.then((_) {	// прочерк вместо параметра для типа void
		print("Выполнение Future завершено");
	});
  
  print("Завершение функции main");
}

Консольный вывод:

Завершение функции main
Hello Future
Выполнение Future завершено

Цепочки колбеков

При необходимости мы можем содавать цепочки методов then, которые будут выполняться друг за другом:

void main() {

	Future<String> future = Future.delayed(
          Duration(seconds: 1), 
          () => "Hello Future"     // возвращаем строку
    );
	
	future.then((value){
		print("Из Future получено значение: $value");
		return 22;
	})
	.then((value) => print("Выполнение Future завершено. Получено: $value") );
  
  print("Завершение функции main");
}

Функция из первого метода then получает из Future результат - строку и выводит ее на консоль. Кроме того, она возвращает новый результат - для примера это число 22.

В функции из второго метода then мы получаем именно это число, а не начальное значение из Future. Кроме того, параметр функции из каждого последующего метода then - это результат функции из предыдущего метода then.

Консольный вывод:

Завершение функции main
Выполняется Future
Из Future получено значение: Hello Future
Выполнение Future завершено. Получено: 22

Обработка ошибок

При работе операции, которую представляет Future, может возникнуть ошибка. В этом случае при переходе в завершенное состояние Future вместо конкретного значения будет хранить информацию об ошибке. Для получения информации об ошибке можно использовать метод catchError(), который работает аналогично методу then - также принимает функцию обратного вызова, в которую передается информация об ошибке:

void main() {

	Future<String> future = Future.delayed(
        Duration(seconds: 1), 
        () { 
            print("Выполняется Future");
            throw "Непредвиденная ошибка"; 
        }
    );	
	
	future.then((value){
		print("Из Future получено значение: $value");
	})
	.catchError((err) => print(err) );
  
  	print("Ждем получения значения из Future");
}

В данном случае в конструкторе Future эмулируем генерацию ошибки с помощью оператора throw (throw "Непредвиденная ошибка";). В качестве объекта ошибки здесь применяется обычная строка ("Непредвиденная ошибка") - это и есть то, что передается в функцию в catchError через параметр err.

В итоге в данном случае функция из метода then не сработает, а сработает функция из catchError, поскольку в процессе выполнения Future возникла ошибка. Консольный вывод:

Ждем получения значения из Future
Выполняется Future
Непредвиденная ошибка

В качестве второго и необязательного параметра метод catchError принимает функцию, которая проверяет соответствие объекта ошибки некоторому условию и, если объект ошибки соответствует этому условию, то возвращается true, иначе возвращается false. Если возвращается false, то есть объект ошибки НЕ соответствует условию, то функция из первого параметра НЕ выполняется.

Рассмотрим пример:

void main() {
     Future<String> future = Future.delayed(
          Duration(seconds: 1), 
          () { 
               print("Выполняется Future");
               throw 404;
          }
     );	
     
     future.then((value){
        print("Из Future получено значение: $value");
     })
     .catchError((err) {
        print("Код ошибки: $err");
     }, 
     test: (err) {
        return err is int;
     });
   
     print("Ждем получения значения из Future");
}

Выражение throw 404 указывает, что объект ошибки представляет число 404, то есть тип int.

В методе catchError второй параметр - функция (err) { return err is int; } через параметр err получает объект ошибки и с помощью выражения err is int проверяет, представляет ли объект ошибки тип int. Если представляет, то возвращается true и выполняется функция (err) { print("Код ошибки: $err");}.

И в данном случае мы получим следующий консольный вывод:

Ждем получения значения из Future
Выполняется Future
Код ошибки: 404

Однако если мы прописали бы генерацию ошибки следующим образом:

throw "404";

То теперь будет передаваться строка, а не число, соответственно выражение return err is int для такой ошибки возвратит false, и она не будет обрабатываться.

Метод whenComplete

Еще один метод - whenComplete выполняет некоторую функцию при завершении Future. Эта функция выполняется вне зависимости возникла во Future ошибка или нет - после всех функций из then и catchError. Например:

void main() {
    Future<String> future = Future.delayed(
        Duration(seconds: 1), 
        () { 
            print("Выполняется Future");
            return "Hello Dart";
        }
    );	
     
    future.then((value){
        print("Из Future получено значение: $value");
    })
    .catchError((err) {
        print("Ошибка: $err");
    })
    .whenComplete(() { print("Future завершил свою работу"); });
   
    print("Ждем получения значения из Future");
}

Консольный вывод:

Ждем получения значения из Future
Выполняется Future
Из Future получено значение: Hello Dart
Future завершил свою работу
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850