Używanie do oczekiwania… lub z synchronicznymi iteracjami

11

MDN mówi, że for await...of ma dwa przypadki użycia:

for await...ofInstrukcja tworzy pętlę iteracji nad asynchroniczny iterable obiektów, jak również na iterables synchronizacji ...

Wcześniej zdawałem sobie sprawę z pierwszego: asynchroniczne iterowanie przy użyciu Symbol.asyncIterator. Ale interesuje mnie teraz to drugie: iteracje synchroniczne.

Poniższy kod iteruje iterację synchroniczną - tablicę obietnic. Wydaje się, że blokuje postęp w wypełnianiu każdej obietnicy.

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)

Zachowanie wydaje się podobne do oczekiwania na każdą obietnicę po kolei, zgodnie z logiką przedstawioną poniżej. Czy to twierdzenie jest prawidłowe?

Pytam, ponieważ ten wzór kodu ma niejawny odrzucenia wire-up pułapki że Promise.alli Promise.allSettledunikania, a wydaje mi się dziwne, że ten wzór będzie wyraźnie wspierane przez język.

Ben Aston
źródło
2
Jakie dokładnie jest twoje pytanie? Wygląda na to, że przykłady, które dostarczyłeś, działają
Sagi Rika
Czy mój opis for await... ofsynchronicznych iteracji jest poprawny, a jeśli tak, to czy ma znaczenie to, że ten wzorzec może emitować nieobsługiwane błędy odrzucenia?
Ben Aston
„Czy to prawda” nie jest pytaniem. „Prawidłowe” to cokolwiek powiesz.
Robert Harvey
Czy potrafisz zademonstrować za pomocą kodu emisje nieobsługiwanych błędów odrzucenia, które opisałeś?
Robert Harvey
Ostateczny kod to pokazuje. W tym kontekście poprawne ma dobrze zdefiniowane znaczenie, ponieważ podałem kod opisujący to, co myślę, że robi. Jeśli zachowanie pasuje do mojego kodu, oznacza to, że mój kod jest poprawny, w przeciwnym razie moje zrozumienie jest nieprawidłowe. Również spostrzeżenie „poprawne” jest tym, co mówisz. jest wyraźnie nieprawdziwe. Prawidłowe ma w tym kontekście dobrze zdefiniowane znaczenie.
Ben Aston

Odpowiedzi:

4

Tak, to dziwne i nie powinieneś tego robić. Nie iteruj tablic obietnic, prowadzi to dokładnie do wspomnianego problemu nieobsługiwanych odrzuceń .

Dlaczego to jest obsługiwane w języku? Kontynuacja niedbałej semantyki.

Dokładne uzasadnienie można znaleźć w tym komentarzu do kwestii omawiającej tę część wniosku :

Myślę, że powinniśmy do tego wrócić, Symbol.iteratorponieważ nasza obecna semantyka Obietnicy polega na umożliwieniu synchronizacji rzeczy jako asynchronicznych. Możesz to nazwać „niechlujstwem”. Wynika to z powyższej logiki @ wód podziemnych , ale chcę bardziej szczegółowo opisać podobieństwa.

Chodzi .thentu o semantykę „łańcuchów” . Możesz zwrócić Obietnicę od .thenlub wartość skalarną; to wszystko jest takie samo. Dzwonisz, Promise.resolveaby nie zawijać czegoś w obietnicy, ale rzucić coś na obietnicę - uzyskać wartość asynchroniczną, gdy masz coś takiego lub innego.

Semantyka asynci awaitdotyczy również niedbałości. Możesz spoliczkować awaitdowolne wyrażenie inne niż Promise w funkcji asynchronicznej i wszystko działa dobrze, dokładnie w ten sam sposób, z wyjątkiem tego, że poddajesz kontrolę kolejce zadań. Podobnie możesz „obronnie” async ominąć wszystko, co chcesz, pod warunkiem, że uzyskasz awaitwynik. Jeśli masz funkcję, która zwraca Obietnicę - cokolwiek! możesz uczynić tę asyncfunkcję i, z perspektywy użytkownika, nic się nie zmieni (nawet jeśli z technicznego punktu widzenia uzyskasz inny obiekt Promise).

Asynchroniczne iteratory i generatory powinny działać w ten sam sposób. Tak jak możesz oczekiwać wartości, która przypadkowo nie była obietnicą, rozsądny użytkownik spodziewałby się, że będzie w stanie yield*zsynchronizować iterator w generatorze asynchronicznym. for awaitPętle powinny podobnie „po prostu działać”, jeśli użytkownik w ten sposób obronnie oznaczy pętlę, sądząc, że może uzyskać iterator asynchroniczny.

Myślę, że złamanie wszystkich tych podobieństw byłoby wielką rzeczą. Sprawiłoby to, że iteratory asynchroniczne byłyby mniej ergonomiczne. Omówmy to następnym razem, gdy generatory / iteratory asynchroniczne pojawią się w porządku obrad na TC39.

Bergi
źródło
Dziękuję Ci. Czy zdarzenie jest emitowane, czy faktycznie jest to jakiś inny błąd? Pytam, ponieważ myślałem, że wydarzenia są częścią interfejsu WebAPI. Czy emisja zdarzeń jest stosowana w podobny sposób, w innych częściach specyfikacji?
Ben Aston
@ 52d6c6af Czy masz na myśli unhandledrejectionwydarzenia?
Bergi
Tak. Wystarczy, że przechwycę „błąd”, którego użyłem w window.addEventListener('unhandledrejection',...skrócie: jest to jedyny przypadek, jaki mogę sobie przypomnieć, z tego rodzaju emisji błędów przez JavaScript. Jednak prawie na pewno się mylę, że tak myślę. Wreszcie: czy emisja tego „błędu” naprawdę naprawdę ma znaczenie poza tym, że w konsoli pojawia się niechciany komunikat o błędzie?
Ben Aston
1
@ 52d6c6af Zobacz tu i tam, jak to określono we wspólnym wysiłku między specyfikacjami ECMAScript i Web API. Nie, wydarzenie nie ma znaczenia, już jest za późno, kiedy je dostaniesz. Afaics, służy tylko do monitorowania błędów po stronie klienta.
Bergi
Jeśli to tak naprawdę nie ma znaczenia, czy rada nadal „nie iteruj tablic obietnic”, czy raczej „pamiętaj, że w pewnych okolicznościach nie zachowuje się szybko”?
Ben Aston
0

sadObietnica nie będąc awaited kiedy to się nie powiedzie - że potrzebuje kod do końca czeka na happyzanim zacznie czekać na sad. sadObietnica zawodzi przed happyustąpienia objawów. ( Promise.alljest narzędziem, które lepiej nadaje się do tego przypadku użycia)

Gershom
źródło
1
Wiem. Stąd moje pytanie. Jeśli Promise.alljest lepsze rozwiązanie, dlaczego język obsługuje tę składnię? for await...ofmożna łatwo wdrożyć, aby po prostu wyliczyć asynchroniczne iterowalne. Ale zadbali o to, by wyliczyć synchroniczne iterowalne (ale z (pozornie?) Pułapką). Dlaczego?
Ben Aston
1
Ach, źle zrozumiałem. Czy pytamy, dlaczego for await ... ofakceptuje synchroniczne iteracje? Wyobrażam sobie, że mogę wspierać generatory asynchroniczne, które warunkowo mogą zwracać elementy synchroniczne.
Gershom
Tak, zwłaszcza, że ​​wydaje się, że wprowadza pułapkę odrzucenia.
Ben Aston
Moim zdaniem pułapka polega bardziej na tworzeniu obietnicy i nie czekaniu na nią od razu. Niestety ta pułapka jest często bardzo przydatną funkcją.
Gershom