🕒 Task vs Promise: Encadenación

Oscar López | Jun 1, 2024 min read

El primer lenguaje en el que aprendí a trabajar de forma asíncrona fue JavaScript. Al principio, me costó mucho trabajo porque era una forma de pensar completamente distinta a la que aprendí en la universidad. Una vez que logré interiorizar los principios de la programación asíncrona, me fue mucho más sencillo. Entonces, cuando comencé a trabajar en C#, de inmediato detecté las similitudes entre las Task y las Promise, pues son prácticamente equivalentes.

Pero al intentar encadenar promesas de la misma forma en la que se hace en JavaScript, me topé con una peculiaridad. La función que se recibe en el método .then de JavaScript es una función que espera el valor que está envuelto en la promesa. Es decir, si tenemos una Promise<number>, la función del .then es una función que recibe un number. En cambio, en C# el “equivalente” a .then es .ContinueWith, pero este método espera una función que recibe una Task del mismo tipo que la Task original. Es decir, si tenemos un Task<string>, el método .ContinueWith recibe una función que recibe un Task<string>. Esto me causó mucha confusión, y conversando con ChatGPT logré tener más claridad en el caso.

En caso de querer revisar mi proceso, esta es la conversación

.then en Javascript

En JavaScript, el método .then se utiliza para manejar el resultado de una promesa. El manejador de .then recibe directamente el valor resuelto de la promesa. Además, JavaScript proporciona el método .catch para manejar errores.

Ejemplo en JavaScript:

fetch('http://example.com')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

En este ejemplo, si la promesa se resuelve, el manejador en el primer .then recibe el response y lo procesa. Si ocurre un error, el manejador en .catch se ejecuta.

.ContinueWith en C#

En C#, el método .ContinueWith de una Task se utiliza para continuar con la ejecución de código después de que una tarea se complete. A diferencia de .then, el manejador de .ContinueWith recibe una instancia de Task<T>, lo que permite acceder a más detalles acerca de la tarea, incluyendo su estado, excepciones y resultado.

Ejemplo básico en C#:

Task<int> task = Task.Run(() => {
    // Simulando una operación asíncrona
    return 42;
});

task.ContinueWith(t => {
    if (t.IsFaulted)
    {
        // Manejar excepciones
        Console.WriteLine($"Error: {t.Exception.InnerException.Message}");
    }
    else if (t.IsCompletedSuccessfully)
    {
        // Manejar el resultado exitoso
        Console.WriteLine($"Resultado: {t.Result}");
    }
});

En este ejemplo, ContinueWith maneja tanto el resultado exitoso como las posibles excepciones. Esto es posible porque ContinueWith proporciona acceso a la tarea completa.

El porqué

No hay .catch en C#

En C#, no existe un método equivalente a .catch de las promesas en JavaScript que se encadene directamente a una Task. En cambio, los errores se manejan dentro del mismo manejador ContinueWith o utilizando bloques try-catch en combinación con await.

Opciones de .ContinueWith en C#

El método .ContinueWith también permite especificar opciones que controlan cuándo se debe ejecutar el manejador de continuación, tales como OnlyOnRanToCompletion y OnlyOnFaulted.

Ejemplo con opciones de ContinueWith:

Task<int> task = Task.Run(() => {
    // Simulando una operación que puede lanzar una excepción
    throw new InvalidOperationException("Error simulado");
    return 42;
});

task.ContinueWith(t => {
    Console.WriteLine($"Resultado: {t.Result}");
}, TaskContinuationOptions.OnlyOnRanToCompletion);

task.ContinueWith(t => {
    Console.WriteLine($"Error: {t.Exception.InnerException.Message}");
}, TaskContinuationOptions.OnlyOnFaulted);

En este ejemplo, se definen dos manejadores de continuación: uno que se ejecuta solo si la tarea se completa con éxito (OnlyOnRanToCompletion) y otro que se ejecuta solo si la tarea falla (OnlyOnFaulted).

Conclusiones

Aunque tanto .ContinueWith en C# como .then en JavaScript sirven para continuar con la ejecución de código después de una operación asíncrona, hay diferencias importantes:

  1. Manejador de Continuación: En JavaScript, el manejador .then recibe el valor resuelto de la promesa. En C#, el manejador .ContinueWith recibe una instancia de Task<T>, proporcionando acceso a más detalles de la tarea.
  2. Manejo de Errores: JavaScript utiliza .catch para manejar errores. En C#, se manejan dentro del manejador ContinueWith o mediante bloques try-catch cuando se usa await.
  3. Opciones de Continuación: C# permite especificar opciones en .ContinueWith para controlar cuándo se debe ejecutar el manejador de continuación, ofreciendo un control más granular.

Estas diferencias reflejan las distintas filosofías y capacidades de los lenguajes, proporcionando a los desarrolladores herramientas poderosas para manejar operaciones asíncronas en cada entorno.

Espero que este artículo te sea útil para entender mejor las diferencias entre .ContinueWith en C# y .then en JavaScript, así como las opciones para manejar errores y acceder a los detalles de las tareas en C#.

comments powered by Disqus