Usando para esperar ... o con iterables sincrónicos

11

MDN dice que for await...of tiene dos casos de uso:

La for await...ofinstrucción crea un ciclo que itera sobre objetos iterables asíncronos, así como en iterables sincronizados, ...

Anteriormente estaba al tanto de lo anterior: los iterables asíncronos usando Symbol.asyncIterator. Pero ahora estoy interesado en lo último: iterables sincrónicos.

El siguiente código itera sobre un iterable síncrono: una serie de promesas. Parece bloquear el progreso en el cumplimiento de cada promesa.

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)

El comportamiento parece ser similar a esperar cada promesa por turno, según la lógica que se muestra a continuación. ¿Es correcta esta afirmación?

Pregunto porque este patrón de código tiene un error implícito de rechazo de conexión Promise.ally Promise.allSettledevitarlo, y me parece extraño que este patrón sea respaldado explícitamente por el lenguaje.

Ben Aston
fuente
2
¿Cuál es exactamente su pregunta? Parece que los ejemplos que proporcionó funcionan
Sagi Rika
¿Es for await... ofcorrecta mi descripción de iterables sincrónicos? De ser así, ¿importa que ese patrón pueda emitir errores de rechazo no controlados?
Ben Aston
"Es correcto" no es una pregunta. "Correcto" es lo que usted dice que es.
Robert Harvey
¿Puede demostrar mediante código la emisión de errores de rechazo no controlados que describió?
Robert Harvey
El código final lo demuestra. Correcto tiene un significado bien definido en este contexto porque he proporcionado el código para describir lo que creo que está haciendo. Si el comportamiento coincide con mi código, entonces mi código es correcto, de lo contrario, mi comprensión es incorrecta. También la observación "Correcto" es lo que usted dice que es. Es claramente falso. Correcto tiene un significado bien definido en este contexto.
Ben Aston

Respuestas:

4

Sí, es extraño, y no debes hacer esto. No repita los arreglos de promesas, esto conduce exactamente al problema de rechazos no controlados que mencionó .

Entonces, ¿por qué se admite esto en el idioma? Para continuar con la promesa descuidada semántica.

Puede encontrar el razonamiento exacto en este comentario del tema que discute esta parte de la propuesta :

Creo que deberíamos recurrir Symbol.iteratorporque nuestra semántica actual de Promise se trata de permitir que las cosas de sincronización se usen como cosas asíncronas. Podrías llamar a esto "descuido". Sigue la lógica de @waterwater anterior , pero solo quiero explicar los paralelos con más detalle.

La semántica de "encadenamiento" .thentiene que ver con esto. Puede devolver una Promesa .theno un valor escalar; todo es lo mismo. Usted llama Promise.resolveno para envolver algo en una Promesa, sino para lanzar algo a una Promesa: obtenga un valor asincrónico cuando tenga algo u otro.

La semántica de asyncy awaitse trata de ser descuidado también. Puede aplicar awaitcualquier expresión que no sea Promesa en una función asíncrona y todo funciona bien, exactamente de la misma manera, excepto que cede el control a la cola de trabajos. Del mismo modo, puede "a la defensiva" poner async lo que quiera, siempre que obtenga awaitel resultado. Si tiene una función que devuelve una Promesa, ¡lo que sea! puede convertirlo en una asyncfunción y, desde la perspectiva del usuario, nada cambia (incluso si, técnicamente, obtiene un objeto Promise diferente).

Los iteradores y generadores asíncronos deberían funcionar de la misma manera. Al igual que puede esperar un valor que, accidentalmente, no era una Promesa, un usuario razonable esperaría poder yield*sincronizar un iterador dentro de un generador asíncrono. for awaitlos bucles deberían "funcionar" de manera similar si un usuario marca defensivamente un bucle de esa manera, pensando que tal vez podría estar obteniendo un iterador asíncrono.

Creo que sería un gran problema romper todos estos paralelos. Haría que los iteradores asíncronos fueran menos ergonómicos. Analicemos esto la próxima vez que los generadores / iteradores asíncronos aparezcan en la agenda de TC39.

Bergi
fuente
Gracias. ¿Se emite un evento o se trata de algún otro tipo de error? Pregunto porque pensé que los eventos eran parte de WebAPI. ¿La emisión de eventos se usa de manera similar, en otras partes de la especificación?
Ben Aston
@ 52d6c6af ¿Te refieres a los unhandledrejectioneventos?
Bergi
Si. Es solo eso para interceptar el "error" que utilicé window.addEventListener('unhandledrejection',...En resumen: es la única instancia que puedo recordar, de este tipo de emisión de error por JavaScript. Sin embargo, es casi seguro que me equivoco al pensar esto. Finalmente: ¿la emisión de este "error" realmente importa más allá de tener un mensaje de error no deseado en la consola?
Ben Aston
1
@ 52d6c6af Vea aquí y allá cómo se especifica esto en un esfuerzo conjunto entre las especificaciones de ECMAScript y Web API. No, el evento realmente no importa, ya es demasiado tarde cuando recibiste esto. Afaics, solo se usa para monitorear errores del lado del cliente.
Bergi
Si realmente no importa, ¿el consejo sigue siendo "no iterar matrices de promesas", o es más bien "tenga en cuenta que esto no exhibe un comportamiento rápido en algunas circunstancias"?
Ben Aston
0

La sadpromesa no está siendo awaited cuando falla - que las necesidades de código para terminar la espera de happyque pueda comenzar a esperar en sad. La sadpromesa está fallando antes de happyresolverse. ( Promise.alles una herramienta más adecuada para este caso de uso)

Gershom
fuente
1
Lo sé. De ahí mi pregunta. Si Promise.alles una mejor solución, ¿por qué el lenguaje atiende esta sintaxis? for await...ofpodría haberse implementado fácilmente para enumerar simplemente iterables asincrónicos. Pero se encargaron de que enumerara iterables sincrónicos (pero con una trampa (¿aparente?)). ¿Por qué?
Ben Aston
1
Ah, no entendí bien. ¿Nos preguntamos por qué for await ... ofacepta iterables sincrónicos? Me imagino que admite generadores asíncronos que condicionalmente pueden devolver elementos sincrónicos.
Gershom
Sí, especialmente dado que parece introducir una trampa de rechazo en el cableado.
Ben Aston
En mi opinión, la trampa es más generalmente al crear una promesa y no esperarla de inmediato. Lamentablemente, esta trampa también es a menudo una característica muy útil.
Gershom