Pular para o conteúdo principal

dart:async

Aprenda sobre os principais recursos da biblioteca dart:async do Dart.

A programação assíncrona frequentemente usa funções de callback, mas o Dart oferece alternativas: os objetos Future e Stream. Um Future é como uma promessa de um resultado que será fornecido em algum momento no futuro. Um Stream é uma forma de obter uma sequência de valores, como eventos. Future, Stream e outros estão na biblioteca dart:async (referência da API).

A biblioteca dart:async funciona tanto em aplicativos web quanto em aplicativos de linha de comando. Para usá-la, importe dart:async:

dart
import 'dart:async';

Future

#

Objetos Future aparecem em toda a biblioteca do Dart, frequentemente como o objeto retornado por um método assíncrono. Quando um future completa, o valor dele está pronto para uso.

Usando await

#

Antes de usar diretamente a API de Future, considere usar await. Código que usa expressões await pode ser mais fácil de entender do que código que usa a API de Future.

Considere a função a seguir. Ela usa o método then() do Future para executar três funções assíncronas em sequência, aguardando cada uma completar antes de executar a próxima.

dart
void runUsingFuture() {
  // ...
  findEntryPoint()
      .then((entryPoint) {
        return runExecutable(entryPoint, args);
      })
      .then(flushThenExit);
}

O código equivalente com expressões await se parece mais com código síncrono:

dart
Future<void> runUsingAsyncAwait() async {
  // ...
  var entryPoint = await findEntryPoint();
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
}

Uma função async pode capturar exceções de Futures. Por exemplo:

dart
var entryPoint = await findEntryPoint();
try {
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
} catch (e) {
  // Handle the error...
}

Para mais informações sobre o uso de await e de outros recursos relacionados da linguagem Dart, veja o tutorial de programação assíncrona.

Uso básico

#

Você pode usar then() para agendar código que executa quando o future completa. Por exemplo, Client.read() retorna um Future, já que requisições HTTP podem levar um tempo. Usar then() permite executar algum código quando esse Future tiver completado e o valor string prometido estiver disponível:

dart
httpClient.read(url).then((String result) {
  print(result);
});

Use catchError() para tratar quaisquer erros ou exceções que um objeto Future possa lançar.

dart
httpClient
    .read(url)
    .then((String result) {
      print(result);
    })
    .catchError((e) {
      // Handle or ignore the error.
    });

O padrão then().catchError() é a versão assíncrona de try-catch.

Encadeando múltiplos métodos assíncronos

#

O método then() retorna um Future, o que oferece uma forma prática de executar múltiplas funções assíncronas em uma ordem específica. Se o callback registrado com then() retorna um Future, then() retorna um Future que completa com o mesmo resultado do Future retornado pelo callback. Se o callback retorna um valor de qualquer outro tipo, then() cria um novo Future que completa com esse valor.

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

No exemplo anterior, os métodos são executados na seguinte ordem:

  1. costlyQuery()
  2. expensiveWork()
  3. lengthyComputation()

Aqui está o mesmo código escrito com await:

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

Aguardando múltiplos futures

#

Às vezes, seu algoritmo precisa invocar várias funções assíncronas e aguardar que todas completem antes de continuar. Use o método estático Future.wait() para gerenciar múltiplos Futures e aguardar que completem:

dart
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!');

Future.wait() retorna um future que completa assim que todos os futures fornecidos tiverem completado. Ele completa com os resultados deles, ou com um erro se qualquer um dos futures fornecidos falhar.

Tratando erros de múltiplos futures

#

Você também pode aguardar operações paralelas em:

  • Um iterable de futures
  • Um record com futures como campos posicionais

Essas extensões retornam um Future com os valores resultantes de todos os futures fornecidos. Diferente de Future.wait, elas também permitem tratar erros.

Se qualquer future da coleção completar com um erro, wait completa com um ParallelWaitError. Isso permite que o chamador trate erros individuais e descarte resultados bem-sucedidos, se necessário.

Quando você não precisa dos valores de resultado de cada future, use wait em um iterable de futures:

dart
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
​
void main() async {
  try {
    // Wait for each future in a list, returns a list of futures:
    var results = await [delete(), copy(), errorResult()].wait;
  } on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
    print(e.values[0]);    // Prints successful future
    print(e.values[1]);    // Prints successful future
    print(e.values[2]);    // Prints null when the result is an error
​
    print(e.errors[0]);    // Prints null when the result is successful
    print(e.errors[1]);    // Prints null when the result is successful
    print(e.errors[2]);    // Prints error
  }
}

Quando você precisa dos valores de resultado individuais de cada future, use wait em um record de futures. Isso traz o benefício adicional de que os futures podem ser de tipos diferentes:

dart
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
​
void main() async {
  try {
    // Wait for each future in a record.
    // Returns a record of futures that you can destructure.
    final (deleteInt, copyString, errorBool) =
        await (delete(), copy(), errorResult()).wait;
​
    // Do something with the results...
  } on ParallelWaitError<
    (int?, String?, bool?),
    (AsyncError?, AsyncError?, AsyncError?)
  > catch (e) {
    // ...
  }
}

Stream

#

Objetos Stream aparecem em toda a API do Dart, representando sequências de dados. Por exemplo, eventos HTML, como cliques em botões, são entregues usando streams. Você também pode ler um arquivo como um stream.

Usando um loop for assíncrono

#

Às vezes, você pode usar um loop for assíncrono (await for) em vez de usar a API de Stream.

Considere a função a seguir. Ela usa o método listen() do Stream para se inscrever em uma lista de arquivos, passando uma função literal que pesquisa cada arquivo ou diretório.

dart
void main(List<String> arguments) {
  // ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = Directory(searchPath);
      startingDir.list().listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(File(searchPath), searchTerms);
    }
  });
}

O código equivalente com expressões await, incluindo um loop for assíncrono (await for), se parece mais com código síncrono:

dart
void main(List<String> arguments) async {
  // ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = Directory(searchPath);
    await for (final entity in startingDir.list()) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(File(searchPath), searchTerms);
  }
}

Para mais informações sobre o uso de await e de outros recursos relacionados da linguagem Dart, veja o tutorial de programação assíncrona.

Ouvindo dados do stream

#

Para obter cada valor conforme ele chega, use await for ou inscreva-se no stream usando o método listen():

dart
// Add an event handler to a button.
submitButton.onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

Neste exemplo, a propriedade onClick é um objeto Stream fornecido pelo botão de envio.

Se você se importa com apenas um evento, pode obtê-lo usando uma propriedade como first, last ou single. Para testar o evento antes de tratá-lo, use um método como firstWhere(), lastWhere() ou singleWhere().

Se você se importa com um subconjunto de eventos, pode usar métodos como skip(), skipWhile(), take(), takeWhile() e where().

Transformando dados do stream

#

Frequentemente, você precisa alterar o formato dos dados de um stream antes de poder usá-los. Use o método transform() para produzir um stream com um tipo diferente de dados:

dart
var lines = inputStream
    .transform(utf8.decoder)
    .transform(const LineSplitter());

Este exemplo usa dois transformers. Primeiro, ele usa utf8.decoder para transformar o stream de inteiros em um stream de strings. Depois, usa um LineSplitter para transformar o stream de strings em um stream de linhas separadas. Esses transformers são da biblioteca dart:convert (veja a seção dart:convert).

Tratando erros e conclusão

#

Como você especifica o código de tratamento de erros e de conclusão depende de usar um loop for assíncrono (await for) ou a API de Stream.

Se você usa um loop for assíncrono, então use try-catch para tratar erros. Código que executa após o stream ser fechado vai depois do loop for assíncrono.

dart
Future<void> readFileAwaitFor() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();
​
  var lines = inputStream
      .transform(utf8.decoder)
      .transform(const LineSplitter());
  try {
    await for (final line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

Se você usa a API de Stream, então trate erros registrando um listener onError. Execute código após o stream ser fechado registrando um listener onDone.

dart
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();
​
inputStream
    .transform(utf8.decoder)
    .transform(const LineSplitter())
    .listen(
      (String line) {
        print('Got ${line.length} characters from stream');
      },
      onDone: () {
        print('file is now closed');
      },
      onError: (e) {
        print(e);
      },
    );

Mais informações

#

Para alguns exemplos de uso de Future e Stream em aplicativos de linha de comando, confira a documentação do dart:io. Veja também estes artigos e tutoriais:

Encontrou essa página sem tradução ou que precisa de correção? Abra uma issue e nos ajude a manter esse site em PT-BR para ajudar pessoas como você.