Saltar al contenido

¿Por qué necesito esperar una función asíncrona cuando supuestamente no devuelve una promesa?

Después de tanto luchar ya hallamos la solución de esta duda que muchos usuarios de este sitio presentan. Si quieres compartir algo puedes aportar tu conocimiento.

Solución:

Todos async las funciones devuelven una promesa. Todos ellos.

Esa promesa eventualmente se resolverá con cualquier valor que devuelva de la función asíncrona.

await solo bloquea la ejecución interna al async función. No bloquea nada fuera de la función. Conceptualmente, una función asincrónica comienza a ejecutarse y tan pronto como llega a un await instrucción, devuelve inmediatamente una promesa incumplida de la función y el mundo de ejecución exterior obtiene esa promesa y continúa ejecutándose.

Algún tiempo después, la promesa interna que estaba siendo awaited se resolverá y luego continuará la ejecución del resto de los componentes internos de la función. Eventualmente, los componentes internos de la función terminarán y devolverán un valor. Eso activará la resolución de la promesa que se devolvió de la función con ese valor de retorno.

Para su información, hay muchas cosas superfluas en su load() función. Puedes cambiarlo de esto:

async function load() 
  const data = await new Promise(resolve => 
    setTimeout(() => resolve([1, 2, 3]), 10);
  ).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: $JSON.stringify(data)`);
  return data;

a esto:

function load() 
    return new Promise(resolve => 
        setTimeout(() => resolve([1, 2, 3]), 10);
    ).then(data => data.map(i => i * 10));

Luego, utilícelo así:

load().then(result => 
    console.log(result);
);

O prefiero encapsular la creación manual de la promesa en su propia función de esta manera:

function delay(t, v) 
    return new Promise(resolve => 
        setTimeout(resolve.bind(null, v), t);
    );


function load() 
    return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));

Y resulta que este pequeño delay() La función es generalmente útil en muchos lugares donde desea retrasar una cadena de promesas.


Gracias a todos los que participaron y me brindaron información. Pero todavía estoy confundido sobre cómo debería usar await y async.

En primer lugar, la mayoría de las veces solo marca una función async si necesitas usar await dentro de la función.

En segundo lugar, usas más comúnmente await (desde dentro de un async función) cuando tiene varias operaciones asincrónicas y desea secuenciarlas, a menudo porque la primera proporciona un resultado que se utiliza como entrada para la segunda. Puedes usar await cuando todo lo que tiene es una sola operación asincrónica, pero realmente no ofrece mucha ventaja sobre una simple .then().

Aquí hay algunos ejemplos de buenas razones para usar async/await:

Secuencia de múltiples operaciones asincrónicas

Imagina que tienes getFromDatabase(), getTheUrl() y getTheContent() que son todos asincrónicos. Si alguno falla, querrá simplemente rechazar la promesa devuelta con el primer error.

Así es como se ve esto sin async / await:

function run() 
    return getFromDatabase(someArg).then(key => 
        return getTheURL(key);
    ).then(url => 
        return getTheContent(url);
    ).then(content => 
         // some final processing
         return finalValue;
    );

Así es como se ve esto con async/await:

async function run(someArg) 
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        

En ambos casos, la función devuelve una promesa que se resuelve con el valor final, por lo que estas dos implementaciones son utilizadas por la persona que llama de la misma manera:

run(someArg).then(finalValue => 
    console.log(finalValue);
).catch(err => 
    console.log(err);
);

Pero, notará que el async/await La implementación tiene un aspecto más serializado y sincrónico y se parece más a un código no asincrónico. Muchos encuentran esto más fácil de escribir, más fácil de leer y más fácil de mantener. Cuanto más procesamiento tenga entre los pasos, incluida la ramificación, más ventajas obtendrá el async/await versión.

Detectar automáticamente tanto las promesas rechazadas como las excepciones sincrónicas

Como dije anteriormente, async las funciones siempre devuelven una promesa. También tienen un manejo de errores incorporado que propaga automáticamente los errores a esa promesa devuelta.

No hace falta decir que si devuelve manualmente una promesa del async función y esa promesa se rechaza, entonces la promesa regresó de la async la función rechazará.

Pero también, si está utilizando await y cualquier promesa que está esperando se rechaza y no tiene un .catch() en la promesa y no tengo un try/catch a su alrededor, la promesa que devuelve la función se rechazará automáticamente. Entonces, de vuelta en nuestro ejemplo anterior de esto:

async function run(someArg) 
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        

Si alguna de las tres promesas que se están haciendo awaited rechazar, entonces la función cortocircuitará (dejará de ejecutar más código en la función) y rechazará la promesa devuelta asíncrona. Entonces, obtienes esta forma de manejo de errores de forma gratuita.

Luego, por último, un async La función también detecta las excepciones sincrónicas y las convierte en una promesa rechazada.

En una función normal que devuelve una promesa como la que teníamos anteriormente:

function run() 
    return getFromDatabase(someArg).then(key => 
        return getTheURL(key);
    ).then(url => 
        return getTheContent(url);
    ).then(content => 
         // some final processing
         return finalValue;
    );

Si getFromDatabase() lanza una excepción sincrónica (quizás activada porque someArg no es válido), entonces esta función general run() lanzará sincrónicamente. Eso significa que para que la persona que llama capte todos los posibles errores de run(), ambos tienen que rodearlo con un try/catch para detectar las excepciones sincrónicas y utilizar un .catch() para atrapar la promesa rechazada:

try 
    run(someArg).then(finalValue => 
        console.log(finalValue);
    ).catch(err => 
        console.log(err);
    );
 catch(e) 
    console.log(err);

Esto es complicado y un poco repetitivo. Pero cuando run() se declara async, entonces NUNCA se lanzará sincrónicamente porque cualquier excepción sincrónica se convierte automáticamente en una promesa rechazada, por lo que puede estar seguro de que está capturando todos los errores posibles cuando se escribe de esta manera:

async function run(someArg) 
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        


// will catch all possible errors from run()
run(someArg).then(finalValue => 
    console.log(finalValue);
).catch(err => 
    console.log(err);
);

¿Debo llamar siempre a todas mis funciones con una espera?

Primero, solo usarías await con una función que devuelve una promesa como await no ofrece ninguna utilidad si la función no devuelve una promesa (solo se agrega al desorden de su código si no es necesario).

En segundo lugar, si usa await o no depende del contexto de la función de llamada (ya que TIENE que estar en un async función a utilizar await y sobre el flujo de la lógica y si se beneficia del uso await o no.

Lugares donde no tiene sentido usar aguardan

async function getKey(someArg) 
    let key = await getFromDatabase(someArg);
    return key;

los await aquí no está haciendo nada útil. No está secuenciando varias operaciones asíncronas y no está procesando el valor de retorno. Puede lograr exactamente el mismo código simplemente devolviendo la promesa directamente:

async function getKey(someArg) 
    return getFromDatabase(someArg);

Y si sabes eso getFromDatabase() nunca lanza sincrónicamente, incluso puede quitar el async de la declaración:

function getKey(someArg) 
    return getFromDatabase(someArg);

Digamos que estoy escribiendo un código compuesto por múltiples funciones dentro de múltiples archivos. Si termino usando una biblioteca que devuelve una Promesa o es una función asíncrona, ¿debo rastrear todas mis llamadas de función desde el punto asincrónico hasta el punto de entrada de la aplicación y agregar una espera antes de todas las llamadas de función después de hacerlas asíncronas?

Esta es una pregunta demasiado general que es difícil de responder en un caso general. Aquí hay algunos pensamientos a lo largo de esta dirección general:

  1. Una vez que cualquier parte de su resultado que está tratando de devolver de su función A() es asincrónico o utiliza cualquier operación asincrónica para obtener, la función en sí es asincrónica. En Javascript simple, nunca puede devolver un resultado asincrónico de forma sincrónica, por lo que su función debe usar un método asincrónico para devolver el resultado (promesa, devolución de llamada, evento, etc.).

  2. Cualquier función B() que llama a tu función asincrónica A() que también está tratando de devolver un resultado basado en lo que obtiene de A() ahora también es asincrónico y también debe comunicar su resultado utilizando un mecanismo asincrónico. Este es true para una función C() que llama B() y necesita comunicar su resultado a la persona que llama. Entonces, puede decir que el comportamiento asincrónico es contagioso. Hasta que llegue a algún punto de la cadena de llamadas en el que ya no necesite comunicar un resultado, todo tiene que utilizar mecanismos asincrónicos para comunicar el resultado, el error y la finalización.

  3. No hay necesidad específica de marcar una función. async a menos que necesite específicamente uno de los beneficios de un async función como la capacidad de utilizar await dentro de esa función o el manejo automático de errores que proporciona. Puede escribir funciones que devuelvan promesas sin problemas sin usar async en la declaración de función. Entonces, “NO”, no vuelvo a subir en la cadena de llamadas haciendo que todo async. Solo hago una función asincrónica si hay una razón específica para hacerlo. Por lo general, esa razón es que quiero usar await dentro de la función, pero también existe la captura automática de excepciones sincrónicas que se convierten en rechazos de promesa que describí anteriormente. Por lo general, no lo necesitaría con un código de buen comportamiento, pero a veces es útil con un código de mal comportamiento o un código con un comportamiento indefinido.

  4. await también se usa solo cuando hay una razón específica para ello. No lo uso automáticamente en cada función que devuelve una promesa. He descrito anteriormente las razones para usarlo. Todavía se puede usar .then() muy bien para procesar el resultado de una única llamada a función que devuelve una promesa. En algunos casos, es solo una cuestión de estilo personal si desea utilizar .then() o await y no hay ninguna razón en particular para que tenga que ser de una forma u otra.

¿O tal vez debería acostumbrarme a llamar a todas mis funciones con una espera, independientemente de si son asíncronas o no?

¡Absolutamente no! En primer lugar, lo último que desea hacer es tomar un código perfectamente sincrónico y convertirlo innecesariamente en asincrónico o incluso hacerlo parecer asincrónico. Código asincrónico (incluso con async y await) es más complicado de escribir, depurar, comprender y mantener que el código síncrono, por lo que nunca querrá convertir innecesariamente código síncrono en código asincrónico agregando async/await en ello:

Por ejemplo, nunca harías esto:

async function random(min, max) 
    let r = await Math.random();
    return Math.floor((r * (max - min)) + min);

En primer lugar, esta es una operación perfectamente sincrónica que se puede codificar así:

function random(min, max) 
    let r = Math.random();
    return Math.floor((r * (max - min)) + min);

Segundo, ese primero async La implementación ha hecho que la función sea mucho más difícil de usar, ya que ahora tiene un resultado asincrónico:

 random(1,10).then(r => 
     console.log(r);
 );

En lugar del simple uso sincrónico:

 console.log(random(1,10));

async/await son solo el azúcar sintáctico, lo que significa que no aportan ninguna funcionalidad nueva al lenguaje, siendo solo envoltorios útiles para las promesas.

Si una función está marcada como async, siempre devuelve una promesa:

> async function f()  return 42; 
undefined
> f()
Promise  42 

Además, si una función es async, usted puede await por cualquier promesa (incluido el resultado de otra async función) dentro de él y la ejecución del código de función se detendrá en await hasta que esa promesa se resuelva o rechace.

Para responder a su pregunta: si usa una función de biblioteca, normalmente sabe si devuelve una promesa o no (y si está marcada como async, seguramente lo hace). Así que asegúrate de await para ello o usar .then con la promesa devuelta.

Calificaciones y reseñas

Acuérdate de que puedes permitirte explicar si descubriste tu problema .

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *