Como posso usar o assíncrono / aguardar no nível superior?

185

Fui revisando async/ awaite depois de revisar vários artigos, decidi testar as coisas sozinho. No entanto, não consigo entender por que isso não funciona:

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

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

O console produz o seguinte (nó v8.6.0):

> fora: [promessa do objeto]

> dentro: Olá

Por que a mensagem de log dentro da função é executada posteriormente? Eu pensei que o motivo async/ awaitfoi criado era para executar a execução síncrona usando tarefas assíncronas.

Existe uma maneira de eu usar o valor retornado dentro da função sem usar um .then()depois main()?

Felipe
fonte
4
Não, apenas as máquinas do tempo podem tornar o código assíncrono síncrono. awaitnão é nada além de açúcar para a thensintaxe da promessa .
Bergi 01/10/19
Por que main retorna um valor? Se for o caso, provavelmente não é um ponto de entrada e precisa ser chamado por outra função (por exemplo, assíncrona IIFE).
Estus Flask
@estus que era apenas um nome de função rápida enquanto eu estava testando coisas em nós, não necessariamente representativa de um programa demain
Felipe
2
FYI, async/awaitfaz parte do ES2017, não do ES7 (ES2016) #
Felix Kling

Respostas:

270

Não consigo entender por que isso não funciona.

Porque mainretorna uma promessa; todas as asyncfunções fazem.

No nível superior, você deve:

  1. Use uma asyncfunção de nível superior que nunca rejeite (a menos que você queira erros de "rejeição sem tratamento") ou

  2. Use thene catch, ou

  3. (Em breve!) Use nível superiorawait , uma proposta que atingiu o Estágio 3 no processo que permite o uso de nível superior awaitem um módulo.

# 1 - asyncFunção de nível superior que nunca rejeita

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

Observe o catch; você deve lidar com exceções de rejeição de promessa / assíncrono, pois nada mais será necessário; você não tem quem ligar para eles. Se preferir, você pode fazer isso com o resultado da chamada através da catchfunção (em vez de try/ catchsintaxe):

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

... que é um pouco mais conciso (eu gosto por esse motivo).

Ou, é claro, não lide com erros e permita apenas o erro "rejeição sem tratamento".

# 2 - thenecatch

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

O catchmanipulador será chamado se ocorrerem erros na cadeia ou no seu thenmanipulador. (Certifique-se de que seu catchmanipulador não gere erros, pois nada está registrado para lidar com eles.)

Ou ambos os argumentos para then:

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

Mais uma vez, observe que estamos registrando um manipulador de rejeição. Mas, neste formulário, verifique se nenhum dos seus thenretornos de chamada não gera erros, nada é registrado para lidar com eles.

Nível 3 awaitem um módulo

Você não pode usar awaitno nível superior de um script que não seja do módulo, mas a proposta de nível superiorawait ( Estágio 3 ) permite usá-lo no nível superior de um módulo. É semelhante ao uso de um asyncwrapper de função de nível superior (nº 1 acima), pois você não deseja que seu código de nível superior rejeite (gere um erro) porque isso resultará em um erro de rejeição sem tratamento. Portanto, a menos que você queira ter essa rejeição sem tratamento quando as coisas derem errado, como no número 1, convém agrupar seu código em um manipulador de erros:

// 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
}

Observe que, se você fizer isso, qualquer módulo que importe do seu módulo aguardará até que a promessa que você está fazendo seja awaitestabelecida; quando um módulo usando o nível superior awaité avaliado, basicamente retorna uma promessa ao carregador de módulos (como faz uma asyncfunção), que espera até que a promessa seja estabelecida antes de avaliar os corpos de qualquer módulo que dependa dele.

TJ Crowder
fonte
Pensar nisso como uma promessa explica agora por que a função retorna imediatamente. I experimentou fazer uma função assíncrona anônima de nível superior e obter resultados que fazem sentido agora
Felipe
2
@ Felipe: Sim, async/ awaitsão açúcar sintáticos em torno de promessas (o bom tipo de açúcar :-)). Você não está pensando nisso como retornando uma promessa; realmente faz. ( Detalhes .)
TJ Crowder
1
@LukeMcGregor - mostrei os dois acima, com todas as asyncopções primeiro. Para a função de nível superior, eu posso vê-la de qualquer maneira (principalmente por causa de dois níveis de recuo na asyncversão).
TJ Crowder
3
@Felipe - Eu atualizei a resposta agora que o alto nível awaitproposta atingiu Stage 3. :-)
TJ Crowder
1
@SurajShrestha - Não. Mas não é um problema que não. :-)
TJ Crowder
7

O nível superiorawait passou para o estágio 3, portanto, a resposta para sua pergunta Como posso usar o assíncrono / aguardar no nível superior? é apenas adicionar awaita chamada para 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)

Ou apenas:

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

Lembre-se de que ele ainda está disponível apenas no [email protected] .

Se você estiver usando o TypeScript , ele chegou à 3.8 .

A v8 adicionou suporte nos módulos.

Também é suportado por Deno (como comentado por gonzalo-bahamondez).

Taro
fonte
Muito legal. Temos algum roteiro para a implementação Node
Felipe
Não sei, mas é muito provável que veremos uma implementação TypeScript e Babel muito em breve. A equipe do TypeScript possui uma política de implementação dos recursos da linguagem estágio 3, e um plug-in Babel geralmente é criado como parte do processo do TC39 para testar propostas. Veja github.com/Microsoft/TypeScript/issues/…
Taro
Também está disponível no deno (apenas js, o typescript ainda não o suporta github.com/microsoft/TypeScript/issues/25988 ) deno.land, consulte deno.news/issues/… .
Gonzalo Bahamondez
SyntaxError: wait é válido apenas na função assíncrona
Sudipta Dhara 28/01
4

A solução real para esse problema é abordá-lo de maneira diferente.

Provavelmente, seu objetivo é algum tipo de inicialização que normalmente ocorre no nível superior de um aplicativo.

A solução é garantir que haja apenas uma única instrução JavaScript no nível superior do seu aplicativo. Se você tiver apenas uma instrução na parte superior do seu aplicativo, poderá usar async / wait em qualquer outro ponto em qualquer lugar (sujeito, é claro, às regras normais de sintaxe)

Dito de outra maneira, envolva todo o seu nível superior em uma função para que ele não seja mais o nível superior e resolva a questão de como executar o assíncrono / aguardar no nível superior de um aplicativo - você não.

É assim que deve ser o nível superior do seu aplicativo:

import {application} from './server'

application();
Duke Dougal
fonte
1
Você está certo de que meu objetivo é a inicialização. Coisas como conexões com o banco de dados, extração de dados etc. Em alguns casos, era necessário obter os dados de um usuário antes de prosseguir com o restante do aplicativo. Essencialmente, você está propondo que application()seja assíncrono?
8288 Felipe
1
Não, estou apenas dizendo que, se houver apenas uma instrução JavaScript na raiz do seu aplicativo, seu problema desaparecerá - a instrução de nível superior, como mostrado, não é assíncrona. O problema é que não é possível usar a assíncrona no nível superior - você não pode esperar para realmente aguardar nesse nível - portanto, se houver apenas uma declaração no nível superior, você terá contornado esse problema. Seu código assíncrono de inicialização agora está inativo em alguns códigos importados e, portanto, o assíncrono funcionará perfeitamente, e você pode inicializar tudo no início do seu aplicativo.
Duke Dougal
1
CORREÇÃO - o aplicativo É uma função assíncrona.
Duke Dougal
4
Eu não estou sendo claro desculpe. O ponto é que normalmente, no nível superior, uma função assíncrona não aguarda ... O JavaScript segue diretamente para a próxima instrução, assim você não pode ter certeza de que seu código de inicialização foi concluído. Se houver apenas uma única declaração na parte superior do seu aplicativo, isso simplesmente não importa.
Duke Dougal
3

Para fornecer mais informações sobre as respostas atuais:

O conteúdo de um node.jsarquivo atualmente é concatenado, de maneira semelhante a uma string, para formar um corpo de função.

Por exemplo, se você possui um arquivo test.js:

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

Em seguida node.js, concatenará secretamente uma função que se parece com:

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

O principal a ser observado é que a função resultante NÃO é uma função assíncrona. Portanto, você não pode usar o termo awaitdiretamente dentro dele!

Mas digamos que você precise trabalhar com promessas nesse arquivo, então existem dois métodos possíveis:

  1. Não use await diretamente dentro da função
  2. Não use await

A opção 1 exige a criação de um novo escopo (e ESTE escopo pode ser async, porque temos controle sobre ele):

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

A opção 2 exige que usemos a API de promessa orientada a objetos (o paradigma menos bonito, mas igualmente funcional de trabalhar com promessas)

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

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

Pessoalmente, espero que, se for viável, o node.js, por padrão, concatene o código em uma asyncfunção. Isso acabaria com essa dor de cabeça.

Gershom
fonte
0

A espera de nível superior é um recurso do próximo padrão EcmaScript. Atualmente, você pode começar a usá-lo com o TypeScript 3.8 (na versão RC no momento).

Como instalar o TypeScript 3.8

Você pode começar a usar o TypeScript 3.8 instalando-o no npm usando o seguinte comando:

$ npm install typescript@rc

No momento, você precisa adicionar a rctag para instalar a versão mais recente do TypeScript 3.8.

Ahmed Bouchefra
fonte
Mas você precisará explicar como usá-lo, então?
raarts
-2

Como main()é executado de forma assíncrona, ele retorna uma promessa. Você precisa obter o resultado no then()método E como os then()retornos também prometem, você deve ligar process.exit()para encerrar o programa.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
fonte
2
Errado. Depois que todas as promessas forem aceitas ou rejeitadas e não houver mais código em execução no encadeamento principal, o processo será encerrado por si só.
@ Dev: normalmente você gostaria de passar valores diferentes exit()para sinalizar se ocorreu um erro.
9000
@ 9000 Sim, mas isso não está sendo feito aqui, e uma vez que um código de saída de 0 é o padrão, não há necessidade de incluí-lo
@ 9000 de fato, o manipulador de erro provavelmente deve estar usandoprocess.exit(1)