1) Co to jest „piekło zwrotne” dla kogoś, kto nie zna javascript i node.js?
To drugie pytanie ma kilka przykładów piekła zwrotnego JavaScript: jak uniknąć długiego zagnieżdżania funkcji asynchronicznych w Node.js
Problem w Javascript polega na tym, że jedynym sposobem na „zamrożenie” obliczenia i umożliwienie wykonania drugiej (asynchronicznej) „reszty” jest umieszczenie „reszty” wewnątrz wywołania zwrotnego.
Na przykład, powiedzmy, że chcę uruchomić kod, który wygląda następująco:
x = getData();
y = getMoreData(x);
z = getMoreData(y);
...
Co się stanie, jeśli teraz chcę, aby funkcje getData były asynchroniczne, co oznacza, że mam szansę uruchomić inny kod, czekając, aż zwrócą swoje wartości? W Javascript jedynym sposobem byłoby przepisanie wszystkiego, co dotyka obliczeń asynchronicznych, przy użyciu stylu przekazywania kontynuacji :
getData(function(x){
getMoreData(x, function(y){
getMoreData(y, function(z){
...
});
});
});
Chyba nie muszę nikogo przekonywać, że ta wersja jest brzydsza od poprzedniej. :-)
2) Kiedy (w jakich ustawieniach) pojawia się „problem z piekłem zwrotnym”?
Kiedy masz dużo funkcji zwrotnych w swoim kodzie! Praca z nimi staje się trudniejsza, im więcej jest ich w kodzie, a szczególnie źle się dzieje, gdy trzeba wykonywać pętle, blokować try-catch i tym podobne.
Na przykład, o ile wiem, w JavaScript jedynym sposobem wykonania szeregu funkcji asynchronicznych, w których jedna jest uruchamiana po poprzednich zwrotach, jest użycie funkcji rekurencyjnej. Nie możesz użyć pętli for.
// we would like to write the following
for(var i=0; i<10; i++){
doSomething(i);
}
blah();
Zamiast tego może być konieczne napisanie:
function loop(i, onDone){
if(i >= 10){
onDone()
}else{
doSomething(i, function(){
loop(i+1, onDone);
});
}
}
loop(0, function(){
blah();
});
//ugh!
Liczba pytań, które otrzymujemy tutaj w StackOverflow, pytając, jak zrobić tego rodzaju rzeczy, świadczy o tym, jak bardzo jest to zagmatwane :)
3) Dlaczego tak się dzieje?
Dzieje się tak, ponieważ w JavaScript jedynym sposobem na opóźnienie obliczeń, tak aby były one uruchamiane po powrocie wywołania asynchronicznego, jest umieszczenie opóźnionego kodu wewnątrz funkcji wywołania zwrotnego. Nie możesz opóźnić kodu, który został napisany w tradycyjnym stylu synchronicznym, więc wszędzie pojawiają się zagnieżdżone wywołania zwrotne.
4) Czy też „piekło zwrotne” może wystąpić również w aplikacji jednowątkowej?
Programowanie asynchroniczne ma do czynienia ze współbieżnością, podczas gdy pojedynczy wątek ma do czynienia z równoległością. Te dwie koncepcje w rzeczywistości nie są tym samym.
Nadal możesz mieć współbieżny kod w kontekście z pojedynczym wątkiem. W rzeczywistości JavaScript, królowa piekła zwrotnego, jest jednowątkowy.
Jaka jest różnica między współbieżnością a równoległością?
5) Czy mógłbyś również pokazać, jak RX rozwiązuje „problem piekła zwrotnego” na tym prostym przykładzie.
W szczególności nie wiem nic o RX, ale zwykle ten problem rozwiązuje się dodając natywną obsługę obliczeń asynchronicznych w języku programowania. Implementacje mogą się różnić i obejmować: async, generatory, coroutines i callcc.
W Pythonie możemy zaimplementować ten poprzedni przykład pętli za pomocą czegoś w rodzaju:
def myLoop():
for i in range(10):
doSomething(i)
yield
myGen = myLoop()
To nie jest pełny kod, ale idea jest taka, że „yield” wstrzymuje naszą pętlę for do momentu, gdy ktoś wywoła myGen.next (). Ważną rzeczą jest to, że nadal moglibyśmy pisać kod przy użyciu pętli for, bez konieczności odwracania logiki „na lewą stronę”, tak jak musieliśmy to zrobić w tej loop
funkcji rekurencyjnej .
Po prostu odpowiedz na pytanie: czy mógłbyś również pokazać, jak RX rozwiązuje „problem piekła zwrotnego” na tym prostym przykładzie?
Magia jest
flatMap
. Możemy napisać następujący kod w Rx dla przykładu @ hugomg:To tak, jakbyś pisał synchroniczne kody FP, ale w rzeczywistości możesz uczynić je asynchronicznymi przez
Scheduler
.źródło
Aby odpowiedzieć na pytanie, jak Rx rozwiązuje piekło zwrotne :
Najpierw opiszmy ponownie piekło oddzwaniania.
Wyobraź sobie przypadek, w którym musimy wykonać http, aby uzyskać trzy zasoby - osobę, planetę i galaktykę. Naszym celem jest znalezienie galaktyki, w której żyje człowiek. Najpierw musimy znaleźć osobę, potem planetę, a na końcu galaktykę. To trzy wywołania zwrotne dla trzech operacji asynchronicznych.
Każde wywołanie zwrotne jest zagnieżdżone. Każde wewnętrzne wywołanie zwrotne jest zależne od swojego rodzica. Prowadzi to do stylu „piramidy zagłady” w stylu callback hell . Kod wygląda jak znak>.
Aby rozwiązać ten problem w RxJs, możesz zrobić coś takiego:
Dzięki operatorowi
mergeMap
AKAflatMap
możesz uczynić to bardziej zwięzłym:Jak widać, kod jest spłaszczony i zawiera pojedynczy łańcuch wywołań metod. Nie mamy „piramidy zagłady”.
W ten sposób unika się piekła zwrotnego.
Jeśli się zastanawiałeś, obietnice to kolejny sposób na uniknięcie piekła oddzwonienia, ale obietnice są chętne , nie leniwe jak obserwowalne i (ogólnie rzecz biorąc) nie możesz ich tak łatwo anulować.
źródło
Piekło zwrotne to każdy kod, w którym użycie funkcji zwrotnych w kodzie asynchronicznym staje się niejasne lub trudne do naśladowania. Generalnie, gdy istnieje więcej niż jeden poziom pośrednictwa, kod wykorzystujący wywołania zwrotne może stać się trudniejszy do śledzenia, trudniejszy do refaktoryzacji i trudniejszy do przetestowania. Zapach kodu to wielopoziomowe wcięcie spowodowane przekazywaniem wielu warstw literałów funkcyjnych.
Dzieje się tak często, gdy zachowanie ma zależności, tj. Gdy A musi się zdarzyć, zanim B musi się wydarzyć przed C. Następnie otrzymujesz taki kod:
Jeśli masz w swoim kodzie wiele zależności behawioralnych, takich jak ten, może to szybko stać się kłopotliwe. Zwłaszcza jeśli się rozgałęzia ...
To nie wystarczy. Jak możemy sprawić, by kod asynchroniczny był wykonywany w określonej kolejności bez konieczności przekazywania wszystkich tych wywołań zwrotnych?
RX jest skrótem od „reaktywnych rozszerzeń”. Nie używałem tego, ale Googling sugeruje, że jest to framework oparty na zdarzeniach, co ma sens. Zdarzenia to typowy wzorzec umożliwiający wykonanie kodu w kolejności bez tworzenia kruchych sprzężeń . Możesz zmusić C do nasłuchiwania zdarzenia „bFinished”, które ma miejsce dopiero po wywołaniu B nasłuchiwania „aFinished”. Następnie możesz łatwo dodać dodatkowe kroki lub rozszerzyć ten rodzaj zachowania i możesz łatwo sprawdzić , czy kod wykonuje się po kolei, po prostu transmitując zdarzenia w przypadku testowym.
źródło
Oddzwanianie do piekła oznacza, że znajdujesz się w środku oddzwaniania lub w innym wywołaniu zwrotnym i przechodzi do n-tego połączenia, dopóki twoje potrzeby nie zostaną spełnione.
Przyjrzyjmy się przykładowi fałszywego wywołania ajax przy użyciu API set timeout, załóżmy, że mamy API receptury, musimy pobrać całą recepturę.
W powyższym przykładzie po upływie 1,5 sekundy, gdy licznik czasu wygaśnie, zostanie wykonany kod wywołania zwrotnego, innymi słowy, za pośrednictwem naszego fałszywego wywołania Ajax cała receptura zostanie pobrana z serwera. Teraz musimy pobrać dane konkretnego przepisu.
Aby pobrać dane konkretnego przepisu, napisaliśmy kod w naszym pierwszym wywołaniu zwrotnym i przekazaliśmy identyfikator przepisu.
Powiedzmy, że musimy pobrać wszystkie przepisy tego samego wydawcy przepisu o identyfikatorze 7638.
Aby w pełni zaspokoić nasze potrzeby, czyli pobrać wszystkie przepisy wydawcy suru, napisaliśmy kod w naszym drugim oddzwonieniu. Jasne jest, że napisaliśmy łańcuch wywołań zwrotnych, który nazywa się piekło zwrotnych.
Jeśli chcesz uniknąć piekła oddzwaniania, możesz użyć Promise, która jest funkcją js es6, każda obietnica przyjmuje wywołanie zwrotne, które jest wywoływane, gdy obietnica jest pełna. obietnica callback ma dwie opcje: zostanie rozwiązana lub odrzucona. Załóżmy, że wywołanie interfejsu API zakończyło się pomyślnie, możesz wywołać metodę rozwiązywania i przekazywać dane przez rozwiązanie , możesz uzyskać te dane za pomocą metody then () . Ale jeśli twój interfejs API zawiódł, możesz użyć odrzucenia, użyj catch, aby złapać błąd. Pamiętaj obietnicę zawsze używać wtedy dla determinacji i połowu do odrzucenia
Rozwiążmy poprzedni problem z piekłem oddzwonienia, używając obietnicy.
Teraz pobierz konkretny przepis:
Teraz możemy napisać inną metodę call allRecipeOfAPublisher, taką jak getRecipe, która również zwróci obietnicę, i możemy napisać inną metodę then (), aby otrzymać obietnicę rozwiązania dla allRecipeOfAPublisher, mam nadzieję, że w tym momencie możesz to zrobić samodzielnie.
Więc nauczyliśmy się, jak konstruować i konsumować obietnice, teraz ułatwmy korzystanie z obietnic, używając async / await, które zostało wprowadzone w es8.
W powyższym przykładzie użyliśmy funkcji async, ponieważ będzie działać w tle, wewnątrz funkcji async użyliśmy słowa kluczowego await przed każdą metodą, która zwraca lub jest obietnicą, ponieważ czekamy na tę pozycję, aż obietnica zostanie spełniona, innymi słowy w poniżej kody, dopóki getIds nie zostaną rozwiązane lub odrzucone, program przestanie wykonywać kody poniżej tej linii, gdy zwrócone zostaną identyfikatory, a następnie ponownie wywołaliśmy funkcję getRecipe () z identyfikatorem i czekaliśmy za pomocą słowa kluczowego await, aż zwrócone zostaną dane. Więc tak w końcu doszliśmy do siebie z piekła oddzwaniania.
Aby użyć await, będziemy potrzebować funkcji asynchronicznej, możemy zwrócić obietnicę, więc użyj jej do rozwiązania obietnicy i odpowiedzi na obietnicę odrzucenia
z powyższego przykładu:
źródło
Jedynym sposobem uniknięcia piekła zwrotnego jest użycie FRP, który jest „ulepszoną wersją” RX.
Niedawno zacząłem używać FRP, ponieważ znalazłem jego dobrą implementację o nazwie
Sodium
( http://sodium.nz/ ).Typowy kod wygląda następująco (Scala.js):
selectedNote.updates()
jest a,Stream
który jest uruchamiany, jeśliselectedNode
(co jestCell
) zmianą,NodeEditorWidget
to odpowiednio aktualizuje się.Czyli w zależności od treści
selectedNode
Cell
aktualnie edytowanaNote
zmieni się.Ten kod całkowicie unika wywołań zwrotnych, prawie, Cacllback-s są wypychane do "zewnętrznej warstwy" / "powierzchni" aplikacji, gdzie logika obsługująca stan łączy się ze światem zewnętrznym. Nie ma wywołań zwrotnych potrzebnych do propagowania danych w ramach wewnętrznej logiki obsługi stanu (która implementuje maszynę stanu).
Pełny kod źródłowy jest tutaj
Fragment kodu powyżej odpowiada poniższemu prostemu przykładowi tworzenia / wyświetlania / aktualizowania:
Ten kod wysyła również aktualizacje do serwera, więc zmiany w zaktualizowanych jednostkach są automatycznie zapisywane na serwerze.
Cała obsługa zdarzeń jest obsługiwana za pomocą
Stream
s iCell
s. To są koncepcje FRP. Wywołania zwrotne są potrzebne tylko wtedy, gdy logika FRP łączy się ze światem zewnętrznym, na przykład wprowadzanie danych przez użytkownika, edycja tekstu, naciśnięcie przycisku, powrót połączenia AJAX.Przepływ danych jest wyraźnie opisany, w sposób deklaratywny przy użyciu FRP (implementowanego przez bibliotekę Sodium), więc do opisu przepływu danych nie jest potrzebna logika obsługi zdarzeń / wywołań zwrotnych.
FRP (który jest bardziej „ścisłą” wersją RX) to sposób na opisanie wykresu przepływu danych, który może zawierać węzły zawierające stan. Zdarzenia wyzwalają zmiany stanu w stanie zawierającym węzły (zwanym
Cell
s).Sód jest biblioteką FRP wyższego rzędu, co oznacza, że używając
flatMap
/switch
prymitywnego można zmienić kolejność wykresu przepływu danych w czasie wykonywania.Polecam zajrzeć do książki Sodium , która szczegółowo wyjaśnia, w jaki sposób FRP pozbywa się wszystkich wywołań zwrotnych, które nie są niezbędne do opisu logiki przepływu danych, która ma związek z aktualizacją stanu aplikacji w odpowiedzi na pewne zewnętrzne bodźce.
Korzystając z FRP, należy zachować tylko te wywołania zwrotne, które opisują interakcję ze światem zewnętrznym. Innymi słowy, przepływ danych jest opisywany w sposób funkcjonalny / deklaratywny, gdy używa się struktury FRP (takiej jak Sodium) lub gdy używa się struktury „podobnej do FRP” (takiej jak RX).
Sód jest również dostępny dla Javascript / Typescript.
źródło
Jeśli nie masz wiedzy na temat oddzwaniania do piekła i oddzwaniania do piekła, nie ma problemu. Pierwszą rzeczą jest to, że oddzwoń i oddzwoń do piekła. o tym zagnieżdżone w języku C, C ++ Zagnieżdżone Oznacza, że klasa znajduje się w innej klasie.
źródło
Użyj https://github.com/Javanile/Jazz.js jazz.js
to upraszcza w ten sposób:
źródło