Radzenie sobie z piramidą wywołania zwrotnego node.js

9

Właśnie zacząłem używać węzła i jedną z rzeczy, które szybko zauważyłem, jest szybkość wywołań zwrotnych do głupiego poziomu wcięcia:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

Oficjalny przewodnik styl mówi umieścić każdy zwrotnego w osobnej funkcji, ale to wydaje się zbyt restrykcyjne w sprawie stosowania zamknięć, a co pojedynczy obiekt zadeklarowane w poziomie górnym dostępnych kilka warstw w dół, a obiekt ma być przekazany przez cały pośrednie wywołania zwrotne.

Czy można w tym celu skorzystać z zakresu funkcji? Umieścić wszystkie funkcje zwrotne, które potrzebują dostępu do obiektu globalnego, w funkcji, która deklaruje ten obiekt, więc przechodzi do zamknięcia?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

i tak dalej przez kilka kolejnych warstw ...

A może istnieją ramy itp., Które pomagają zmniejszyć poziomy wcięć bez deklarowania nazwanej funkcji dla każdego pojedynczego wywołania zwrotnego? Jak sobie radzisz z piramidą oddzwaniania?

Thecoop
źródło
2
Zwróć uwagę na dodatkowy problem z zamknięciami - w zamknięciu JS przechwytuje cały kontekst nadrzędny (w innych językach przechwytuje tylko zmienne używane lub te, które użytkownik specjalnie zażądał), powodując miłe wycieki pamięci, jeśli hierarchia wywołania zwrotnego jest wystarczająco głęboka i, na przykład, callback jest gdzieś zachowany.
Eugene

Odpowiedzi:

7

Obietnice zapewniają czysty rozdział obaw między zachowaniem asynchronicznym a interfejsem, dzięki czemu funkcje asynchroniczne można wywoływać bez wywołań zwrotnych, a interakcję wywołań zwrotnych można wykonywać na interfejsie ogólnych obietnic.

Istnieje wiele wdrożeń „obietnic”:


Na przykład możesz przepisać zagnieżdżone wywołania zwrotne

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

lubić

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Zamiast oddzwaniania w oddzwanianiu a(b(c()))łączysz łańcuch „.then” a().then(b()).then(c()).


Wprowadzenie tutaj: http://howtonode.org/promises

Fabien Sa
źródło
czy mógłbyś wyjaśnić więcej o tym, co robią te zasoby i dlaczego polecasz je jako odpowiedź na zadane pytanie? „Tylko odpowiedzi” nie są mile widziane na Stack Exchange
gnat
1
Ok przepraszam. Dodałem przykład i więcej informacji.
Fabien Sa
3

Jako alternatywę dla obietnic powinieneś przyjrzeć się yieldsłowu kluczowemu w połączeniu z funkcjami generatora, które zostaną wprowadzone w EcmaScript 6. Oba są dostępne dzisiaj w kompilacjach Node.js 0.11.x, ale wymagają dodatkowo określenia --harmonyflagi podczas uruchamiania Node .js:

$ node --harmony app.js

Stosując te konstrukty i biblioteki takie jak TJ Holowaychuk za co pozwala na pisanie kodu asynchronicznego w stylu, który wygląda jak kod synchronicznym, chociaż nadal działa w sposób asynchroniczny. Zasadniczo te rzeczy razem implementują wspólną obsługę Node.js.

Zasadniczo, musisz napisać funkcję generatora kodu, który uruchamia rzeczy asynchroniczne, wywołać tam rzeczy asynchroniczne, ale poprzedzić je yieldsłowem kluczowym. W końcu twój kod wygląda następująco:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Aby uruchomić tę funkcję generatora, potrzebujesz biblioteki takiej jak wspomniana wcześniej co. Połączenie wygląda wtedy następująco:

co(run);

Lub, mówiąc wprost:

co(function * () {
  // ...
});

Należy pamiętać, że z funkcji generatora można wywoływać inne funkcje generatora, wystarczy tylko yieldponownie je poprzedzić .

Aby uzyskać wprowadzenie do tego tematu, wyszukaj w Google hasła takie jak yield generators es6 async nodejsi powinieneś znaleźć mnóstwo informacji. Przyzwyczajenie się do tego zajmuje trochę czasu, ale kiedy już go dostaniesz, nie chcesz wracać.

Należy pamiętać, że nie tylko zapewnia to lepszą składnię do wywoływania funkcji, ale także pozwala korzystać ze zwykłych (synchronicznych) elementów logiki przepływu sterowania, takich jak forpętle lub try/ catch. Nigdy więcej bałaganu z wieloma oddzwanianiami i wszystkimi tymi rzeczami.

Powodzenia i miłej zabawy :-)!

Golo Roden
źródło
0

Masz teraz pakiet asyncawait z bardzo ścisłą składnią do tego, co powinno być przyszłym rodzimym wsparciem dla await& asyncin Node.

Zasadniczo pozwala na pisanie kodu asynchronicznego wyglądającego synchronicznie , znacznie zmniejszając poziomy LOC i wcięcia, z kompromisem niewielkiej utraty wydajności (liczby właścicieli pakietów są 79% prędkości w porównaniu do nieprzetworzonych wywołań zwrotnych), miejmy nadzieję, że zmniejszony, gdy dostępna będzie obsługa natywna.

Nadal dobry wybór, aby wydostać się z oddzwaniania piekło / piramida koszmaru zagłady IMO, gdy wydajność nie jest najważniejsza i gdzie synchroniczny styl pisania lepiej odpowiada potrzebom projektu.

Podstawowy przykład z pakietu doc:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Frosty Z
źródło