Saltar al contenido principal

dart:async

Aprende sobre las características principales de la biblioteca dart:async de Dart.

La programación asíncrona a menudo utiliza funciones callback, pero Dart proporciona alternativas: objetos Future y Stream. Un Future es como una promesa de un resultado que se proporcionará en algún momento en el futuro. Un Stream es una forma de obtener una secuencia de valores, como eventos. Future, Stream y más están en la biblioteca dart:async (referencia de la API).

La biblioteca dart:async funciona tanto en aplicaciones web como en aplicaciones de línea de comandos. Para usarla, importa dart:async:

dart
import 'dart:async';

Futuro

#

Los objetos Future aparecen por todas las librerías de Dart, a menudo como el objeto devuelto por un método asíncrono. Cuando un future se completa, su valor está listo para usar.

Usar await

#

Antes de utilizar directamente la API de Future, considera usar await en su lugar. El código que utiliza expresiones await puede ser más fácil de entender que el código que utiliza la API de Future.

Considera la siguiente función. Utiliza el método then() de Future para ejecutar tres funciones asíncronas consecutivamente, esperando que cada una se complete antes de ejecutar la siguiente.

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

El código equivalente con expresiones await se parece más al código síncrono:

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

Una función async puede capturar excepciones de los Futures. Por ejemplo:

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

Para obtener más información sobre el uso de await y las características relacionadas del lenguaje Dart, consulta el tutorial de programación asíncrona.

Uso básico

#

Puedes usar then() para programar código que se ejecute cuando el future se complete. Por ejemplo, Client.read() devuelve un Future, ya que las solicitudes HTTP pueden tardar un tiempo. Usar then() te permite ejecutar algún código cuando ese Future se haya completado y el valor de cadena prometido esté disponible:

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

Usa catchError() para manejar cualquier error o excepción que un objeto Future pueda lanzar.

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

El patrón then().catchError() es la versión asíncrona de try-catch.

Encadenar múltiples métodos asíncronos

#

El método then() devuelve un Future, proporcionando una forma útil de ejecutar múltiples funciones asíncronas en un orden determinado. Si el callback registrado con then() devuelve un Future, then() devuelve un Future que se completará con el mismo resultado que el Future devuelto por el callback. Si el callback devuelve un valor de cualquier otro tipo, then() crea un nuevo Future que se completa con dicho valor.

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

En el ejemplo anterior, los métodos se ejecutan en el siguiente orden:

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

Aquí está el mismo código escrito usando await:

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

Esperar múltiples futures

#

A veces, tu algoritmo necesita invocar muchas funciones asíncronas y esperar a que todas se completen antes de continuar. Usa el método estático Future.wait() para administrar múltiples Futures y esperar a que se completen:

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() devuelve un future que se completa una vez que todos los futures proporcionados se han completado. Se completa ya sea con sus resultados, o con un error si alguno de los futures proporcionados falla.

Manejo de errores para múltiples futures

#

También puedes esperar operaciones en paralelo en:

  • Un iterable de futures
  • Un record con futures como campos posicionales

Estas extensiones devuelven un Future con los valores resultantes de todos los futures proporcionados. A diferencia de Future.wait, también te permiten manejar errores.

Si cualquier future de la colección se completa con un error, wait se completa con un ParallelWaitError. Esto permite al llamador manejar errores individuales y descartar los resultados exitosos si es necesario.

Cuando no necesitas los valores resultantes de cada future, usa wait en un 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
  }
}

Cuando necesitas los valores de resultados individuales de cada future, usa wait en un record de futures. Esto proporciona la ventaja adicional de que los futures pueden ser de diferentes tipos:

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

#

Los objetos Stream aparecen en las API de Dart, representando secuencias de datos. Por ejemplo, los eventos HTML como los clics en botones se entregan utilizando streams. También puedes leer un archivo como un stream.

Usar un bucle for asíncrono

#

A veces puedes usar un bucle for asíncrono (await for) en lugar de usar la API de Stream.

Considera la siguiente función. Utiliza el método listen() de Stream para suscribirse a una lista de archivos, pasando una función literal que busca en cada archivo o directorio.

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);
    }
  });
}

El código equivalente con expresiones await, incluyendo un bucle for asíncrono (await for), se parece más al 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 obtener más información sobre el uso de await y las características relacionadas del lenguaje Dart, consulta el tutorial de programación asíncrona.

Escuchar datos de un stream

#

Para obtener cada valor a medida que llega, usa await for o suscríbete al stream utilizando el 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();
});

En este ejemplo, la propiedad onClick es un objeto Stream proporcionado por el botón de envío.

Si solo te importa un evento, puedes obtenerlo usando una propiedad como first, last o single. Para probar el evento antes de manejarlo, usa un método como firstWhere(), lastWhere() o singleWhere().

Si te importa un subconjunto de eventos, puedes usar métodos como skip(), skipWhile(), take(), takeWhile() y where().

Transformar datos de un stream

#

A menudo, necesitas cambiar el formato de los datos de un stream antes de poder utilizarlos. Usa el método transform() para producir un stream con un tipo de datos diferente:

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

Este ejemplo utiliza dos transformadores. Primero usa utf8.decoder para transformar el stream de enteros en un stream de cadenas. Luego usa un LineSplitter para transformar el stream de cadenas en un stream de líneas separadas. Estos transformadores son de la biblioteca dart:convert (consulta la sección de dart:convert).

Manejo de errores y finalización

#

La forma de especificar el código de manejo de errores y de finalización depende de si utilizas un bucle for asíncrono (await for) o la API de Stream.

Si utilizas un bucle for asíncrono, usa try-catch para manejar los errores. El código que se ejecuta después de cerrar el stream va después del bucle for así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);
  }
}

Si utilizas la API de Stream, maneja los errores registrando un oyente onError. Ejecuta código después de cerrar el stream registrando un oyente 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);
      },
    );

Más información

#

Para ver algunos ejemplos de uso de Future y Stream en aplicaciones de línea de comandos, consulta la documentación de dart:io. Consulta también estos artículos y tutoriales: