Usando o await fora de uma função assíncrona

86

Eu estava tentando encadear duas funções assíncronas, porque a primeira tinha um parâmetro de retorno condicional que fazia a segunda executar ou sair do módulo. No entanto, encontrei um comportamento estranho que não consigo encontrar nas especificações.

async function isInLobby() {
    //promise.all([chained methods here])
    let exit = false;
    if (someCondition) exit = true;
}

Este é um snippet bastardizado do meu código (você pode ver o escopo completo aqui ), que simplesmente verifica se um jogador já está em um lobby, mas isso é irrelevante.

Em seguida, temos essa função assíncrona.

async function countPlayer() {
    const keyLength = await scardAsync(game);
    return keyLength;
}

Esta função não precisa ser executada se exit === true.

Eu tentei fazer

const inLobby = await isInLobby();

Esperava que este fosse aguardar os resultados, então posso usar inLobbypara executar condicionalmente countPlayer, porém recebi um erro de tipo sem detalhes específicos.

Por que você não pode awaituma asyncfunção fora do escopo da função? Eu sei que é uma promessa de açúcar, então deve ser acorrentada, thenmas por que em countPlayereu posso esperar outra promessa, mas fora, eu não posso await isInLobby?

Sterling Archer
fonte
Você pode nos mostrar onde você fez await isInLobby()e como inLobbyé usado? Além disso, onde / como é countPlayerchamado?
Bergi
@Bergi Eu vinculei meu repo ao contexto real. Demasiado código para questionar
Sterling Archer
Não vejo onde está o problema com isso (talvez você já atualizou o repo)? Se você se referir à isInLobby().then( … countPlayer().then …parte, a solução é trivial: basta fazer a função na qual essas chamadas estão contidas ( (req, res) =>aquela) async.
Bergi
@Bergi, o problema não é que estava quebrado, mas funciona como está. Eu simplesmente não entendia por que a espera de alto nível não era uma coisa. Acontece que ele ainda não existe sem definir o escopo de todo o módulo como uma função assíncrona
Sterling Archer
Mas você nem mesmo precisa de nível superior await para o seu código? É por isso que me perguntei se você aceitou a resposta que realmente não se relaciona com o problema da pergunta.
Bergi

Respostas:

74

O nível superior awaitnão é compatível. Existem algumas discussões pelo comitê de padrões sobre o motivo disso, como este problema no Github .

Há também um artigo no Github sobre por que esperar de nível superior é uma má ideia. Especificamente, ele sugere que, se você tiver um código como este:

// data.js
const data = await fetch( '/data.json' );
export default data;

Agora, qualquer arquivo importado data.jsnão será executado até que a busca seja concluída, portanto, todo o carregamento do módulo está bloqueado. Isso torna muito difícil raciocinar sobre a ordem do módulo do app, já que estamos acostumados com o Javascript de nível superior executando de forma síncrona e previsível. Se isso fosse permitido, saber quando uma função é definida torna-se complicado.

Minha perspectiva é que não é uma boa prática seu módulo ter efeitos colaterais simplesmente por carregá-lo. Isso significa que qualquer consumidor de seu módulo terá efeitos colaterais simplesmente exigindo seu módulo. Isso limita muito onde seu módulo pode ser usado. Um nível superior awaitprovavelmente significa que você está lendo alguma API ou chamando algum serviço no momento do carregamento. Em vez disso, você deve apenas exportar funções assíncronas que os consumidores possam usar em seu próprio ritmo.

Andy Ray
fonte
Esse é um bom link, obrigado. É uma pena que o suporte de nível superior não esteja lá. Espero que seja. Atualmente, tenho que aninhar minhas promessas aqui e isso é uma prática muito ruim e não gosto disso. :( Obrigado.
Sterling Archer
@SterlingArcher, como alternativa, use um IIFE assíncrono:void async function() { const inLobby = await isInLobby() }()
robertklep
@robertklep isso não abrangeria inLobbya função tornando-a inacessível?
Sterling Archer
@SterlingArcher sim, iria requerer que você movesse todo o seu código para lá (basicamente tornando-o o "nível superior"). É apenas uma alternativa ao uso .then()(desculpe, deveria ter deixado isso um pouco mais claro).
robertklep
não concordo, o usuário de data.js é 'bloqueado', no entanto, enquanto carrega o próprio data.js e todas as suas dependências, essa noção de 'bloqueio' não é ruim por si só. O nível superior await pode ser considerado como carregando alguma dependência que aparentemente se precisa ter antes de o uso ser 'lançado'.
Ibrahim ben Salah
128

Sempre há isso, é claro:

(async () => {
    await ...

    // all of the script.... 

})();
// nothing else

Isso torna uma função rápida com async onde você pode usar await. Isso elimina a necessidade de criar uma função assíncrona, o que é ótimo! // créditos Silve2611

digerati-stratagies
fonte
3
Elabore mais e explique por que isso funciona.
Paul Back
12
é muito estúpido votar negativamente esta resposta. Faz uma função rápida com async onde você pode usar o await. Isso elimina a necessidade de criar uma função assíncrona, o que é ótimo!
Silve2611
55
Não resolve o problema, já que você ainda precisa awaitdessa função anônima, que novamente, não funciona fora das funções.
Michael
então, como isso lidaria com uma condição de erro? ele também cai dentro do código de espera?
Manuel Hernandez
Tks, isso é uma grande ajuda, especialmente para a promessa de retorno de chamada assim que buscar a API de login, que é respondida perfeitamente para obter armazenamento getItem
tess hsu
10

Melhor ainda é colocar um ponto-e-vírgula adicional antes do bloco de código

;(async () => {
    await ...
})();

Isso evita que o formatador automático (por exemplo, em vscode) mova o primeiro parênteses para o final da linha anterior.

O problema pode ser demonstrado no seguinte exemplo:

const add = x => y => x+y
const increment = add(1)
(async () => {
    await ...
})();

Sem o ponto-e-vírgula, isso será reformatado como:

const add = x => y => x+y
const increment = add(1)(async () => {
  await Promise(1)
})()

o que obviamente está errado porque atribui a função assíncrona como yparâmetro e tenta chamar uma função a partir do resultado (que na verdade é uma string estranha '1async () => {...}')

Viliam Simko
fonte
20
Não é o reformatador que está errado. Sem um ponto-e-vírgula, é assim que sua função seria analisada em tempo de execução e você obteria um erro, quebra de linha ou nenhuma quebra de linha. A solução correta em seu exemplo seria adicionar um ponto- add(1);e- vírgula depois e não antes da função assíncrona.
Madara's Ghost
11
Além disso, eu honestamente não entendo como isso se relaciona com a questão em questão.
Madara's Ghost
Sim, você está certo. Além disso, o tempo de execução precisa do ponto-e-vírgula.
Viliam Simko
1

você pode fazer o nível superior esperar desde o typescript 3.8
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#-top-level-await
Do post:
Isso ocorre porque anteriormente em JavaScript (junto com a maioria das outras linguagens com um recurso semelhante), await só era permitido dentro do corpo de uma função assíncrona. No entanto, com o await de nível superior, podemos usar o await no nível superior de um módulo.

const response = await fetch("...");
const greeting = await response.text();
console.log(greeting);

// Make sure we're a module
export {};

Observe que há uma sutileza: aguardar de nível superior funciona apenas no nível superior de um módulo, e os arquivos são considerados módulos apenas quando o TypeScript encontra uma importação ou exportação. Em alguns casos básicos, pode ser necessário escrever export {} como um boilerplate para ter certeza disso.

Aguardar de nível superior pode não funcionar em todos os ambientes em que você poderia esperar neste momento. Atualmente, você só pode usar o nível superior esperar quando a opção do compilador alvo for es2017 ou superior e o módulo for esnext ou sistema. O suporte em vários ambientes e bundlers pode ser limitado ou pode exigir a ativação do suporte experimental.

MACHADO
fonte