Saltar al contenido principal

Escribe código asíncrono

Aprende sobre programación asíncrona en Dart y cómo hacer solicitudes HTTP.

En este capítulo, explorarás la programación asíncrona en Dart, lo que permitirá a tus aplicaciones realizar múltiples tareas de forma concurrente. Aprenderás a obtener datos de internet usando el paquete http para recuperar el resumen de un artículo de Wikipedia.

Qué lograrás

Escribe código asíncrono con futures y await
Instala y usa paquetes externos
Obtén datos en tiempo real de la API de Wikipedia

Prerrequisitos

#
  • Haber completado el Capítulo 2, que cubrió la sintaxis básica de Dart y la interacción con la línea de comandos. Deberías tener un proyecto dartpedia configurado.
  • Familiaridad con el concepto de APIs (Application Programming Interfaces) como una forma de obtener datos.

Tareas

#

En este capítulo, modificarás la aplicación de CLI dartpedia existente para obtener y mostrar un resumen de artículo usando el paquete http y técnicas de programación asíncrona.

Tarea 1: Agregar la dependencia http

#

Antes de que puedas realizar solicitudes HTTP, necesitas agregar el paquete http como una dependencia a tu proyecto.

  1. Abre el archivo dartpedia/cli/pubspec.yaml dentro de tu proyecto. Este archivo se llama pubspec, y gestiona los metadatos de tu proyecto Dart, las dependencias (como el paquete http) y los assets.

  2. Localiza la sección dependencies.

  3. Agrega http: ^1.4.0 (o la última versión estable) bajo dependencies. El símbolo ^ permite usar versiones compatibles.

    yaml
    dependencies:
      http: ^1.4.0
    
  4. Guarda el archivo pubspec.yaml.

  5. Ejecuta dart pub get en tu terminal desde el directorio dartpedia/cli. Este comando obtiene la dependencia recién agregada y la hace disponible para su uso en tu proyecto.

    Deberías ver una salida similar a esta:

    bash
    Resolving dependencies...
    Downloading packages...
    + http 1.4.0
      lints 5.1.1 (6.0.0 available)
    Changed 1 dependency!
    1 package has newer versions incompatible with dependency constraints.
    Try `dart pub outdated` for more information.
    

Tarea 2: Importar el paquete http

#

Ahora que has agregado el paquete http, necesitas importarlo en tu archivo Dart para usar sus funcionalidades.

  1. Abre el archivo dartpedia/bin/cli.dart.

  2. Agrega la siguiente sentencia import en la parte superior del archivo, junto con el import de dart:io existente:

    dart
    import 'dart:io';
    import 'package:http/http.dart' as http; // Add this line
    

    Esta línea importa el paquete http y le da el alias http. Después de hacer esto, puedes referirte a las clases y funciones dentro del paquete http usando http. (por ejemplo, http.Client, http.get). La parte as http es una convención estándar para evitar conflictos de nombres si otra librería importada también tiene una clase o función con un nombre similar.

Tarea 3: Implementar la función getWikipediaArticle

#

Ahora crea una nueva función llamada getWikipediaArticle que se encargue de obtener datos de una API externa. Esta función será async porque las solicitudes de red son operaciones asíncronas.

  1. Define la firma de la función: Debajo de tu función main (y de la función printUsage), añade la siguiente firma de función.

    dart
    // ... (your existing printUsage() function)
    
    Future<String> getWikipediaArticle(String articleTitle) async {
      //You'll add more code here soon
    }
    

    Aspectos destacados del código anterior:

    • El tipo de retorno Future<String> indica que esta función eventualmente producirá un resultado de tipo String, pero no de inmediato, ya que es una operación asíncrona.
    • La palabra clave async marca la función como asíncrona, lo que te permite usar await dentro de ella.
  2. Construye la URL de la API: Dentro de tu nueva función getWikipediaArticle, crea un objeto Uri. El Uri representa el endpoint de la API de Wikipedia al que llamarás para obtener el resumen de un artículo.

    Añade estas líneas dentro de la función getWikipediaArticle:

    dart
    Future<String> getWikipediaArticle(String articleTitle) async {
      final url = Uri.https(
        'en.wikipedia.org', // Wikipedia API domain
        '/api/rest_v1/page/summary/$articleTitle', // API path for article summary
      );
      // ...
    }
    
  3. Realiza la solicitud HTTP y maneja la respuesta: Ahora, usa la función de nivel superior get de package:http para realizar una solicitud HTTP GET a la URL que acabas de construir. La palabra clave await pausa la ejecución de getWikipediaArticle hasta que se complete la llamada get y devuelva un objeto http.Response objeto.

    Después de que se complete la solicitud, verifica el response.statusCode para asegurarte de que la solicitud fue exitosa (un código de estado de 200 significa OK). Si tiene éxito, devuelve el response.body, que contiene los datos obtenidos (en este caso, JSON sin procesar). Si la solicitud falla, devuelve un mensaje de error informativo.

    Añade estas líneas después de la construcción del Uri dentro de getWikipediaArticle:

    dart
    Future<String> getWikipediaArticle(String articleTitle) async {
      final url = Uri.https(
        'en.wikipedia.org',
        '/api/rest_v1/page/summary/$articleTitle',
      );
      final response = await http.get(url); // Make the HTTP request
    
      if (response.statusCode == 200) {
        return response.body; // Return the response body if successful
      }
    
      // Return an error message if the request failed
      return 'Error: Failed to fetch article "$articleTitle". Status code: ${response.statusCode}';
    }
    

Tarea 4: Integrar la llamada a la API en searchWikipedia

#

Integrarás la llamada a la API en searchWikipedia. Esta función albergará la lógica principal para manejar el comando wikipedia.

  1. Actualiza searchWikipedia para usar async: Localiza tu función searchWikipedia y actualiza su firma a async, ya que ahora realizará operaciones asíncronas.

    Tu función searchWikipedia ahora debería verse así:

    dart
    // ... (your existing main function)
    
    void searchWikipedia(List<String>? arguments) async { // Added 'async'
      final String articleTitle;
    
      // If the user didn't pass in arguments, request an article title.
      if (arguments == null || arguments.isEmpty) {
        print('Please provide an article title.');
        // Read input and provide a default empty string if the input is null.
        articleTitle = stdin.readLineSync() ?? '';
      } else {
        // Otherwise, join the arguments into a single string.
        articleTitle = arguments.join(' ');
      }
    
      print('Looking up articles about "$articleTitle". Please wait.');
      print('Here ya go!');
      print('(Pretend this is an article about "$articleTitle")');
    }
    
    // ... (your existing printUsage() function)
    

    Aspectos destacados del código anterior:

    • void searchWikipedia(List<String>? arguments) async: La función ahora es async. Esto es esencial porque llamará a getWikipediaArticle, que es una función async en sí misma y necesitará hacer await de su resultado.
  2. Rechaza entradas null o cadenas de texto vacías: En este momento, si el usuario ejecuta dart bin/cli.dart search y presiona Enter sin escribir un título, stdin.readLineSync() devuelve una cadena vacía. Si el usuario presiona Control+D o Control+Z para enviar una señal de fin de archivo (EOF), stdin.readLineSync() devuelve null, y ?? '' recurre a una cadena vacía. En cualquier caso, la CLI realiza una solicitud ficticia a Wikipedia con una cadena vacía como título del artículo.

    Reemplaza la alternativa de seguridad ?? '' con una verificación que imprima un mensaje y finalice la función cuando no se proporcione una entrada válida.

    dart
    void searchWikipedia(List<String>? arguments) async {
      final String articleTitle;
    
      if (arguments == null || arguments.isEmpty) {
        print('Please provide an article title.');
        // Read input without the `?? ''` fallback.
        final inputFromStdin = stdin.readLineSync();
        if (inputFromStdin == null || inputFromStdin.isEmpty) {
          print('No article title provided. Exiting.');
          return; // Exit the function if there's no valid input.
        }
        articleTitle = inputFromStdin;
      } else {
        // Otherwise, join the arguments into a single string.
        articleTitle = arguments.join(' ');
      }
    
      print('Looking up articles about "$articleTitle". Please wait.');
      print('Here ya go!');
      print('(Pretend this is an article about "$articleTitle")');
    }
    
  3. Llama a getWikipediaArticle e imprime el resultado: Reemplaza las dos líneas print de marcador de posición al final de searchWikipedia con una llamada a getWikipediaArticle y un único print del resultado.

    dart
    void searchWikipedia(List<String>? arguments) async {
      final String articleTitle;
    
      if (arguments == null || arguments.isEmpty) {
        print('Please provide an article title.');
        final inputFromStdin = stdin.readLineSync();
        if (inputFromStdin == null || inputFromStdin.isEmpty) {
          print('No article title provided. Exiting.');
          return;
        }
        articleTitle = inputFromStdin;
      } else {
        articleTitle = arguments.join(' ');
      }
    
      print('Looking up articles about "$articleTitle". Please wait.');
    
      // Call the API and await the result.
      var articleContent = await getWikipediaArticle(articleTitle);
      print(articleContent); // Print the full article response (raw JSON for now)
    }
    

    Aspectos destacados del código anterior:

    • await getWikipediaArticle(articleTitle): Debido a que getWikipediaArticle es una función async, necesitas hacer await de su resultado. Esto pausa la función searchWikipedia hasta que el Future<String> devuelto por getWikipediaArticle se resuelva en un String que contenga el contenido del artículo.
    • print(articleContent): Imprime el resumen del artículo obtenido como una cadena JSON sin procesar en la consola.

Tarea 5: Actualizar main para llamar a searchWikipedia

#

Finalmente, actualiza tu función main para llamar a la nueva función searchWikipedia cuando se use el comando wikipedia.

  1. Localiza el bloque else if en tu función main que actualmente maneja el comando search. Cambia el nombre del comando de search a wikipedia y actualiza la llamada a la función.

    En el código de ejemplo, main no hace await de la llamada a searchWikipedia, lo que significa que main en sí no necesita ser marcada como async.

    Tu función main ahora debería verse así:

    dart
    // ... (existing const version declaration and printUsage function)
    
    void main(List<String> arguments) {
      if (arguments.isEmpty || arguments.first == 'help') {
        printUsage();
      } else if (arguments.first == 'version') {
        print('Dartpedia CLI version $version');
      } else if (arguments.first == 'wikipedia') { // Changed to 'wikipedia'
        // Pass all arguments *after* 'wikipedia' to searchWikipedia
        final inputArgs = arguments.length > 1 ? arguments.sublist(1) : null;
        searchWikipedia(inputArgs); // Call searchWikipedia (no 'await' needed here for main)
      } else {
        printUsage(); // Catch all for any unrecognized command.
      }
    }
    
    • arguments.sublist(1): Esto extrae todos los elementos de la lista arguments, comenzando desde el segundo elemento (índice 1). Esto elimina de manera efectiva el propio comando wikipedia, de modo que searchWikipedia solo recibe los argumentos reales del título del artículo.
    • searchWikipedia(inputArgs): Esto llama a searchWikipedia directamente. Dado que main no necesita hacer nada después de que searchWikipedia se complete, no necesitas hacer await de ello desde main. Por lo tanto, main no necesita ser async.

Tarea 6: Ejecutar la aplicación

#

Ahora que has implementado la solicitud http y la has integrado en tu aplicación, pruébala.

  1. Abre tu terminal y ejecuta el siguiente comando:

    bash
    dart run bin/cli.dart wikipedia "Dart_(programming_language)"
    
  2. Verifica para asegurarte de que la aplicación obtuvo el resumen del artículo "Dart" de la API de Wikipedia e imprimió la respuesta JSON sin procesar en la consola. Podrías ver algo como:

    bash
    Looking up articles about "Dart_(programming_language)". Please wait.
    {
      "type": "standard",
      "title": "Dart (programming language)",
      "displaytitle": "<span class=\"mw-page-title-main\">Dart (programming language)</span>",
      "namespace": {
          "id": 0,
          "text": ""
        }
    
      // ... (rest of the JSON output will be present but truncated here)
    
    }
    
  3. A continuación, intenta ejecutar sin argumentos. Por ejemplo, cuando se te solicite, ingresa "Flutter_(software)".

    bash
    dart run bin/cli.dart wikipedia
    
    bash
    Please provide an article title.
    Flutter_(software)
    Looking up articles about "Flutter_(software)". Please wait.
    {
      "type": "standard",
      "title": "Flutter (software)",
      "displaytitle": "<span class=\"mw-page-title-main\">Flutter (software)</span>",
      "namespace": {
          "id": 0,
          "text": ""
      }
    
    // ... (rest of the JSON output will be present but truncated here)
    
    }
    

    ¡Ahora has implementado con éxito el comando básico wikipedia que obtiene datos reales de una API externa!

Revisión

#

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Dominaste futures, async y await

Aprendiste que Future representa un valor que estará disponible más tarde. La palabra clave async keyword marca las funciones como asíncronas, y await pausa la ejecución hasta que se complete un Future.

Agregaste el paquete http como dependencia

Modificaste pubspec.yaml para agregar el paquete http, ejecutaste dart pub get para obtenerlo, y lo importaste con un alias (as http). Puedes seguir estos pasos para agregar cualquier paquete externo desde pub.dev.

Obtuviste datos en tiempo real de Wikipedia

Construiste getWikipediaArticle() para realizar solicitudes HTTP GET utilizando la función get de package:http, construiste URIs, verificaste códigos de estado y devolviste el cuerpo de la respuesta. ¡Tu CLI ahora obtiene datos reales de la web!

Cuestionario

#

Pon a prueba tu comprensión

1 / 4
What keyword is used to mark a function as asynchronous in Dart?
  1. await

    No exactamente.

    await se usa dentro de funciones asíncronas para pausar y esperar los resultados. No marca una función como asíncrona por sí misma.

  2. asynchronous

    No exactamente.

    Aunque es descriptiva, asynchronous no es una palabra clave de Dart. Dart usa una palabra clave más corta para marcar funciones como asíncronas.

  3. future

    No exactamente.

    Future es un tipo que representa un valor que vendrá después, no una palabra clave para marcar funciones.

  4. async

    ¡Correcto!

    La palabra clave async marca una función como asíncrona, lo que le permite usar await para esperar operaciones asíncronas.

What does a Future<String> represent in Dart?
  1. Una cadena que se almacena en una ubicación de memoria separada.

    No exactamente.

    El tipo Future no se trata de ubicaciones de memoria o almacenamiento. Piensa en lo que significa "future" en el lenguaje cotidiano.

  2. Un valor de cadena que estará disponible en algún momento en el futuro.

    ¡Correcto!

    Un Future es como una promesa. Representa un valor que aún no está disponible pero que lo estará una vez que finalice una operación asíncrona (como una solicitud de red).

  3. Una cadena que podría ser null.

    No exactamente.

    Las cadenas que aceptan nulos tienen un tipo String?. Un Future<String> representa un valor de cadena que estará disponible más tarde, después de que se complete una operación async.

  4. Una lista de cadenas que puede crecer con el tiempo.

    No exactamente.

    Una lista de cadenas que puede crecer sería List<String>. Future<T> envuelve un único valor de tipo T, no una colección.

What does await do when placed before a Future?
  1. Convierte el Future en una operación síncrona.

    No exactamente.

    La operación sigue siendo asíncrona. await cambia la forma en que escribes el código, no cómo se ejecuta.

  2. Pausa la función hasta que se completa el Future, luego devuelve el valor desempaquetado.

    ¡Correcto!

    await pausa la ejecución de la función hasta que se complete el Future.

  3. Ejecuta el Future en un hilo separado para un mejor rendimiento.

    No exactamente.

    Dart no usa hilos automáticamente de esta manera. await funciona dentro del modelo de bucle de eventos de hilo único de Dart.

  4. Cancela el Future si tarda demasiado.

    No exactamente.

    await no cancela nada ni establece tiempos de espera. Simplemente espera, independientemente de cuánto tiempo tome la operación.

While your code is waiting for an HTTP response with await, what happens to other parts of your application?
  1. Todo se congela hasta que llega la respuesta.

    No exactamente.

    Si todo se congelara, async no ofrecería ningún beneficio. Piensa en por qué existe la programación asíncrona.

  2. Dart inicia automáticamente un nuevo hilo para manejar la espera.

    No exactamente.

    Dart utiliza un modelo de bucle de eventos de un solo hilo. await funciona dentro de este modelo sin crear nuevos hilos.

  3. Otras operaciones asíncronas y manejadores de eventos pueden continuar ejecutándose.

    ¡Correcto!

    Este es el beneficio clave de la programación async. Mientras espera por una operación, el bucle de eventos de Dart puede manejar otro trabajo, manteniendo tu aplicación receptiva.

  4. El programa termina si la respuesta tarda demasiado.

    No exactamente.

    Los programas no terminan por esperar en operaciones async. No hay un tiempo de espera integrado que cause la terminación.

Siguiente lección

#

En el próximo capítulo, te enfocarás en organizar nuestro código en librerías y paquetes reutilizables. Refactorizarás tu aplicación para mejorar su estructura y mantenibilidad creando un paquete separado para manejar los argumentos de línea de comandos.