¿Cómo puedo usar async / await en el nivel superior?

185

He estado revisando async/ awaity después de revisar varios artículos, decidí probar las cosas yo mismo. Sin embargo, parece que no puedo entender por qué esto no funciona:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

La consola genera lo siguiente (nodo v8.6.0):

> fuera: [promesa de objeto]

> dentro: Hola

¿Por qué el mensaje de registro dentro de la función se ejecuta después? Pensé que la razón async/ awaitfue creado fue para realizar una ejecución sincrónica usando tareas asincrónicas.

¿Hay alguna manera de usar el valor devuelto dentro de la función sin usar un .then()after main()?

Felipe
fuente
44
No, solo las máquinas del tiempo pueden hacer que el código asíncrono sea síncrono. awaitno es más que azúcar para la thensintaxis de promesa .
Bergi
¿Por qué main devuelve un valor? Si debería, probablemente no sea un punto de entrada y deba ser llamado por otra función (por ejemplo, async IIFE).
Estus Flas el
@estus era solo un nombre de función rápido mientras estaba probando cosas en el nodo, no necesariamente representativo de un programamain
Felipe
2
FYI, async/awaites parte de ES2017, no ES7 (ES2016)
Felix Kling

Respuestas:

270

Parece que no puedo entender por qué esto no funciona.

Porque maindevuelve una promesa; todas las asyncfunciones lo hacen.

En el nivel superior, debes:

  1. Utilice una asyncfunción de nivel superior que nunca rechace (a menos que desee errores de "rechazo no controlado"), o

  2. Usar theny catch, o

  3. (¡Próximamente!) Utilice el nivel superiorawait , una propuesta que ha alcanzado la Etapa 3 en el proceso que permite el uso de nivel superior awaiten un módulo.

# 1 - asyncFunción de nivel superior que nunca rechaza

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

Note el catch; debe manejar los rechazos de promesa / excepciones asíncronas, ya que nada más lo hará ; no tienes quien llama para pasarlos. Si lo prefiere, puede hacerlo con el resultado de llamarlo a través de la catchfunción (en lugar de try/ catchsintaxis):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... que es un poco más conciso (por eso me gusta).

O, por supuesto, no maneje los errores y solo permita el error de "rechazo no controlado".

# 2 - thenycatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

Se catchllamará al controlador si se producen errores en la cadena o en su thencontrolador. (Asegúrese de que su catchcontrolador no arroje errores, ya que no hay nada registrado para manejarlos).

O ambos argumentos para then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

Nuevamente, observe que estamos registrando un controlador de rechazo. Pero de esta forma, asegúrese de que ninguna de sus thendevoluciones de llamada no arroje ningún error, no hay nada registrado para manejarlos.

# 3 de nivel superior awaiten un módulo

No se puede usar awaiten el nivel superior de un script que no sea de módulo, pero la propuesta de nivel superiorawait ( Etapa 3 ) le permite usarlo en el nivel superior de un módulo. Es similar al uso de un asynccontenedor de funciones de nivel superior (# 1 arriba) en que no desea que su código de nivel superior rechace (arroje un error) porque eso dará como resultado un error de rechazo no controlado. Entonces, a menos que desee tener ese rechazo no controlado cuando las cosas salen mal, como con el n. ° 1, querrá incluir su código en un controlador de errores:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

Tenga en cuenta que si hace esto, cualquier módulo que importe desde su módulo esperará hasta que se cumpla la promesa que está awaitcumpliendo; cuando awaitse evalúa un módulo que utiliza el nivel superior , básicamente devuelve una promesa al cargador de módulos (como lo hace una asyncfunción), que espera hasta que se cumpla esa promesa antes de evaluar los cuerpos de los módulos que dependen de él.

TJ Crowder
fuente
Pensarlo como una promesa explica ahora por qué la función regresa de inmediato. Experimenté con la creación de una función asincrónica anónima de nivel superior y ahora obtengo resultados que tienen sentido
Felipe
2
@Felipe: Sí, async/ awaitson azúcar sintáctica alrededor de las promesas (el buen tipo de azúcar :-)). No sólo estás pensando en él como una promesa de regresar; En realidad lo hace. ( Detalles .)
TJ Crowder
1
@LukeMcGregor: mostré los dos anteriores, con la asyncopción todo primero. Para la función de nivel superior, puedo verla de cualquier manera (principalmente debido a dos niveles de sangría en la asyncversión).
TJ Crowder
3
@Felipe - He actualizado la respuesta ahora que la awaitpropuesta de nivel superior ha alcanzado la Etapa 3. :-)
TJ Crowder
1
@SurajShrestha - No. Pero no es un problema que no sea así. :-)
TJ Crowder
7

El nivel superior seawait ha movido a la etapa 3, por lo que la respuesta a su pregunta ¿Cómo puedo usar async / wait en el nivel superior? es simplemente agregar awaitla llamada a main():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

O solo:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Tenga en cuenta que todavía solo está disponible en [email protected] .

Si está utilizando TypeScript , aterrizó en 3.8 .

v8 ha agregado soporte en módulos.

También es compatible con Deno (como comentó gonzalo-bahamondez).

Taro
fuente
Muy genial. ¿Tenemos alguna hoja de ruta para una implementación de Nodo
Felipe
No lo sé, pero es muy probable que veamos una implementación de TypeScript y Babel muy pronto. El equipo de TypeScript tiene una política de implementación de las características del lenguaje de la etapa 3, y generalmente se crea un complemento de Babel como parte del proceso TC39 para probar las propuestas. Ver github.com/Microsoft/TypeScript/issues/…
Taro
También está disponible en deno (solo js, ​​typecript aún no lo admite github.com/microsoft/TypeScript/issues/25988 ) deno.land ver deno.news/issues/… .
Gonzalo Bahamondez
SyntaxError: waitit solo es válido en la función asíncrona
Sudipta Dhara
4

La solución real a este problema es abordarlo de manera diferente.

Probablemente su objetivo es algún tipo de inicialización que generalmente ocurre en el nivel superior de una aplicación.

La solución es garantizar que solo haya una única declaración de JavaScript en el nivel superior de su aplicación. Si solo tiene una declaración en la parte superior de su aplicación, puede usar async / wait en cualquier otro lugar (sujeto a las reglas de sintaxis normales)

Dicho de otra manera, envuelva todo su nivel superior en una función para que ya no sea el nivel superior y eso resuelva la cuestión de cómo ejecutar async / wait en el nivel superior de una aplicación, no lo hace.

Así debería verse el nivel superior de su aplicación:

import {application} from './server'

application();
Duque dugal
fuente
1
Tienes razón en que mi objetivo es la inicialización. Cosas como conexiones de bases de datos, extracción de datos, etc. En algunos casos era necesario obtener los datos de un usuario antes de continuar con el resto de la aplicación. ¿Esencialmente estás proponiendo que application()sea ​​asíncrono?
Felipe
1
No, solo digo que si solo hay una declaración de JavaScript en la raíz de su aplicación, su problema desaparecerá: la declaración de nivel superior como se muestra no es asíncrona. El problema es que no es posible usar asíncrono en el nivel superior; no puede esperar para realmente esperar en ese nivel; por lo tanto, si solo hay una declaración en el nivel superior, entonces ha evitado ese problema. Su código asíncrono de inicialización ahora está inactivo en algún código importado y, por lo tanto, asíncrono funcionará bien, y puede inicializar todo al inicio de su aplicación.
Duque Dougal
1
CORRECCIÓN: la aplicación ES una función asíncrona.
Duque Dougal
44
No estoy siendo claro lo siento. El punto es que normalmente, en el nivel superior, una función asíncrona no espera ... JavaScript va directamente a la siguiente declaración, por lo que no puede estar seguro de que su código de inicio se haya completado. Si solo hay una sola declaración en la parte superior de la aplicación, eso simplemente no importa.
Duque Dougal
3

Para dar más información sobre las respuestas actuales:

El contenido de un node.jsarchivo está actualmente concatenado, de forma similar a una cadena, para formar un cuerpo de función.

Por ejemplo, si tiene un archivo test.js:

// Amazing test file!
console.log('Test!');

Luego node.jssecretamente concatenará una función que se ve así:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

Lo más importante a tener en cuenta es que la función resultante NO es una función asíncrona. ¡Entonces no puedes usar el término awaitdirectamente dentro de él!

Pero supongamos que necesita trabajar con las promesas en este archivo, entonces hay dos métodos posibles:

  1. No lo use await directamente dentro de la función
  2. No usar await

La opción 1 requiere que asynccreemos un nuevo alcance (y ESTE alcance puede serlo , porque tenemos control sobre él):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

La opción 2 requiere que usemos la API de promesa orientada a objetos (el paradigma menos bonito pero igualmente funcional de trabajar con promesas)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

Personalmente espero que, si es viable, node.js concatene el código por defecto en una asyncfunción. Eso eliminaría este dolor de cabeza.

Gershom
fuente
0

El nivel superior de espera es una característica del próximo estándar EcmaScript. Actualmente, puede comenzar a usarlo con TypeScript 3.8 (en la versión RC en este momento).

Cómo instalar TypeScript 3.8

Puede comenzar a usar TypeScript 3.8 instalándolo desde npm usando el siguiente comando:

$ npm install typescript@rc

En este momento, debe agregar la rcetiqueta para instalar la última versión de mecanografiado 3.8.

Ahmed Bouchefra
fuente
¿Pero entonces tendrás que explicar cómo usarlo?
raarts
-2

Como se main()ejecuta de forma asincrónica, devuelve una promesa. Tienes que obtener el resultado en el then()método. Y debido a que las then()devoluciones también son prometedoras, debe llamar process.exit()para finalizar el programa.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
fuente
2
Incorrecto. Una vez que se han aceptado o rechazado todas las promesas y no se está ejecutando más código en el hilo principal, el proceso termina por sí solo.
@Dev: normalmente le gustaría pasar valores diferentes exit()para indicar si ocurrió un error.
9000
@ 9000 Sí, pero eso no se está haciendo aquí, y dado que el código de salida 0 es el predeterminado, no es necesario incluirlo
@ 9000, de hecho, el controlador de errores probablemente debería estar usandoprocess.exit(1)