Asynchroniczność w Node.js

Callbacks

Asynchroniczna funkcja jako jeden z argumentów wejściowych może przyjmować tzw. funkcję callback, która ma zostać wywołana po zakończeniu jej działania. Przykładowo, funkcję readFile z modułu fs można wywołać w następujący sposób:

fs.readFile([ścieżka_pliku], [callback]);

Funkcja ta wczytuje plik z podanej ścieżki, a następnie wywołuje funkcję [callback]. Nie wiadomo, kiedy ta funkcja zostanie wywołana; wiadomo tylko, że wydarzy się to po wczytaniu pliku. Kod programu, który znajduje się pod wywołaniem readFile, może (choć nie musi) zostać wykonany zanim zostanie wywołana funkcja [callback]. Na przykład:

const fs = require('fs');

fs.readFile("my_file.txt", (err, data) => {
  if (err) throw err;
  console.log("Plik został wczytany");
});

console.log("Funkcja readFile została wywołana")
Funkcja readFile została wywołana
Plik został wczytany

Piramidy zguby

Problem z używaniem funkcji callback pojawia się, gdy kilka operacji asynchronicznych trzeba wywołać po kolei – np. sięgać kilka razy do bazy danych, za każdym razem korzystając z wyników poprzednich zapytań. Użycie funkcji callback wymaga wówczas zagnieżdżania wielu funkcji, co może pogorszyć czytelność kodu:

function zróbCośZłożonego(dane) {
  operacja1(dane, (wynik1) => {
    operacja2(wynik1, (wynik2) => {
      operacja3(wynik2, (wynik3) => {
        operacja4(wynik3);
      });
    });
  });
}

Kod programu, zawierający liczne zagnieżdżone funkcje callback – jak powyżej – jest trudny w utrzymaniu. Jeśli wystąpi w nim błąd, to trudno jest ten błąd zidentyfikować. Między innymi z tego powodu styl programowania, przedstawiony w powyższym przykładzie, często opatrywany jest nazwami mającymi zniechęcić do jego naśladowania, jak np. callback hell albo pyramid of doom. Używanie promises (opisane w następnym podrozdziale) pozwala na realizację asynchroniczności wolną od tego problemu.


Literatura