Usando para aguardar ... de com iterables síncronos

11

O MDN diz que for await...of tem dois casos de uso:

A for await...ofinstrução cria um loop que itera sobre objetos iteráveis ​​assíncronos, bem como sobre iteráveis ​​de sincronização, ...

Eu já tinha conhecimento do primeiro: async iterables usando Symbol.asyncIterator. Mas agora estou interessado no último: iterables síncronos.

O código a seguir itera sobre uma iterável síncrona - uma matriz de promessas. Parece bloquear o progresso no cumprimento de cada promessa.

async function asyncFunction() {
    try {
        const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
        const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
        const promises = [happy, sad]
        for await(const item of promises) {
            console.log(item)
        }
    } catch (err) {
        console.log(`an error occurred:`, err)
    }
}

asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)

O comportamento parece ser semelhante a aguardar cada promessa por sua vez, de acordo com a lógica mostrada abaixo. Esta afirmação está correta?

Eu pergunto porque esse padrão de código tem uma armadilha fio-up rejeição implícita de que Promise.alle Promise.allSettledevitar, e parece estranho para mim que esse padrão seria explicitamente suportada pelo idioma.

Ben Aston
fonte
2
Qual é a tua pergunta exatamente? Parece que os exemplos que você forneceu funcionam
Sagi Rika 16/03
Minha descrição de for await... ofiterables síncronos está correta e, em caso afirmativo, importa que esse padrão possa emitir erros de rejeição sem tratamento?
Ben Aston
"Está correto" não é uma pergunta. "Correto" é o que você diz.
Robert Harvey
Você pode demonstrar via código a emissão de erros de rejeição sem tratamento que você descreveu?
Robert Harvey
O código final demonstra isso. Correct tem um significado bem definido nesse contexto, porque forneci o código para descrever o que acho que está fazendo. Se o comportamento corresponder ao meu código, ele estará correto, caso contrário, meu entendimento está incorreto. Além disso, a observação "Correto" é o que você diz. é claramente falso. Correto tem um significado bem definido neste contexto.
Ben Aston

Respostas:

4

Sim, é estranho, e você não deve fazer isso. Não itere séries de promessas, pois isso leva exatamente ao problema de rejeições não tratadas que você mencionou .

Então, por que isso é suportado no idioma? Para continuar com a semântica da promessa desleixada.

Você pode encontrar o raciocínio exato neste comentário da questão que discute esta parte da proposta :

Acho que deveríamos Symbol.iteratorrecorrer porque nossa semântica atual do Promise visa permitir que coisas de sincronização sejam usadas como coisas assíncronas. Você pode chamar isso de "negligência". Segue a lógica da água subterrânea acima , mas eu só quero detalhar os paralelos com mais detalhes.

A semântica do "encadeamento" .thené sobre isso. Você pode retornar uma promessa .thenou um valor escalar; é tudo a mesma coisa. Você chama Promise.resolvenão para agrupar algo em uma promessa, mas para converter algo em uma promessa - obtenha um valor assíncrono quando tiver uma coisa ou outra.

A semântica de asynce awaité tudo sobre ser desleixado também. Você pode dar awaitum tapa em qualquer expressão que não seja Promise em uma função assíncrona e tudo funcionará bem, exatamente da mesma maneira, exceto que você gera controle para a fila de trabalhos. Da mesma forma, você pode "defender" async o que quiser, contanto que você awaitobtenha o resultado. Se você tem uma função que retorna uma promessa - tanto faz! você pode transformar isso em uma asyncfunção e, da perspectiva do usuário, nada muda (mesmo que, tecnicamente, você obtenha um objeto Promise diferente).

Os iteradores e geradores assíncronos devem funcionar da mesma maneira. Assim como você pode esperar um valor que, acidentalmente, não era uma Promessa, um usuário razoável esperaria poder yield*sincronizar um iterador dentro de um gerador assíncrono. for awaitos loops da mesma forma "simplesmente funcionam" se um usuário marcar defensivamente um loop dessa maneira, pensando que talvez esteja recebendo um iterador assíncrono.

Eu acho que seria muito importante quebrar todos esses paralelos. Isso tornaria os iteradores assíncronos menos ergonômicos. Vamos discutir isso na próxima vez que geradores / iteradores assíncronos aparecerem na agenda do TC39.

Bergi
fonte
Obrigado. Um evento é emitido ou é outro tipo de erro? Eu pergunto porque achei que os eventos faziam parte da WebAPI. A emissão de eventos é usada de maneira semelhante, em outras partes da especificação?
Ben Aston
@ 52d6c6af Você está se referindo aos unhandledrejectioneventos?
Bergi 16/03
Sim. É só isso para interceptar o "erro" que usei. window.addEventListener('unhandledrejection',...Em resumo: é o único exemplo que posso lembrar desse tipo de emissão de erro por JavaScript. Estou quase certamente errado em pensar isso, no entanto. Finalmente: a emissão desse "erro" realmente importa além de ter uma mensagem de erro indesejada no console?
Ben Aston
11
@ 52d6c6af Veja aqui e ali como isso é especificado em um esforço conjunto entre as especificações do ECMAScript e da API da Web. Não, o evento realmente não importa, já é tarde demais quando você conseguiu isso. Afaics, é usado apenas para monitorar erros do lado do cliente.
Bergi 16/03
Se isso realmente não importa, o conselho ainda "não repete matrizes de promessas" ou é "esteja ciente de que isso não exibe um comportamento à prova de falhas em algumas circunstâncias"?
Ben Aston
0

A sadpromessa não está sendo cumprida awaitquando falha - esse código precisa terminar a espera happyantes que possa começar a esperar sad. A sadpromessa está falhando antes de happyresolver. ( Promise.allé uma ferramenta mais adequada para este caso de uso)

Gershom
fonte
11
Eu sei. Daí a minha pergunta. Se Promise.allé uma solução melhor, por que o idioma atende a essa sintaxe? for await...ofpoderia facilmente ter sido implementado para simplesmente enumerar iterables assíncronos. Mas eles serviram para enumerar iteráveis ​​síncronos (mas com uma (aparentemente?) Armadilha). Por quê?
Ben Aston
11
Ah, eu entendi errado. Estamos perguntando por que for await ... ofaceita iterables síncronos? Eu imaginaria suportar geradores assíncronos que condicionalmente podem retornar itens síncronos.
Gershom
Sim, especialmente porque parece introduzir uma armadilha de rejeição.
Ben Aston
Na minha opinião, a armadilha está mais na criação de uma promessa e não na expectativa imediata. Infelizmente, essa armadilha também costuma ser um recurso muito útil.
Gershom