Jak działa callback w JavaScript

Tak, jak to bywa z wieloma pojęciami w programowaniu coś, co jest łatwe i oczywiste dla doświadczonych programistek i programistów, sprawia czasem wiele kłopotów tym początkującym. Czasem zanim dane pojęcie „kliknie” w głowie, może przysporzyć uczącej się osobie sporo frustracji.

Jednym z takich pojęć jest z pewnością tzw. callback. W tym artykule spróbuję przestawić to zagadnienie tak, aby stało się zrozumiałe nawet dla osób, które dopiero zaczynają swoją przygodę z programowaniem w JavaScript. Do czytania zachęcam również tych już trochę bardziej doświadczonych. Być może coś nowego „poukłada się” w głowie i sprawy staną się jeszcze bardziej oczywiste.

Zacznijmy od początku

Zacznijmy od krótkiego przypomnienia czym są funkcje i jak możemy korzystać z wyników ich działania.

W uproszczeniu funkcja to po prostu fragment kodu, który zamykamy w nawiasach klamrowych { }, nadajemy mu jakąś nazwę (albo i nie) i uruchamiamy go poprzez wywołanie, czyli podanie jej nazwy wraz z nawiasami okrągłymi ( ). Jeśli funkcja przyjmuje jakieś parametry, to możemy podać ich wartości w nawiasach przy wywołaniu.

Rozważmy prostą funkcję sum, która przyjmuje dwie liczby i dodaje je do siebie.

				
					function sum(x,y) {
    const c = x + y;
}
				
			

Aby skorzystać z funkcji, wystarczy ją wywołać z liczbami, które chcemy dodać.

				
					sum(3,5);
				
			

Hmm tylko gdzie jest nasz wynik? Spróbujmy to zilustrować.

Callback JavaScript - Wyjaśnienie jak działa funkcja

Może brzmi to jak żart „suchar” ale myślę, że wiele osób natknęło się na podobną pułapkę na początku nauki programowania. To, że funkcja coś oblicza wcale nie oznacza, że wynik koniecznie musi zostać „wypowiedziany” (czyli zwrócony) po skończeniu obliczeń. Zobaczmy, jak możemy rozwiązać ten problem.

Korzystanie z wyniku działań funkcji

To, w jaki sposób przekazany będzie wynik działania naszej funkcji, możemy podzielić na dwa główne podejścia:

1. Wynik zostanie zwrócony, czyli „wypowiedziany” przez funkcję.

2. Wewnątrz funkcji wynik zostanie wykorzystany do dalszych działań.

Oba te zachowania muszą zostać zaprogramowane na etapie projektowania funkcji.

JavaScript Callback - Wyjaśnienie jak można uzyskać wynik obliczeń z funkcji

1. Powiedz mi wynik, a ja zajmę się resztą

W pierwszym podejściu projektujemy funkcję tak, aby po obliczeniu „powiedziała” wynik czyli go zwróciła. Następnie taki wynik jest przez nas wykorzystywany do kolejnych czynności.

Aby funkcja „powiedziała” nam wynik, musimy użyć słowa kluczowego return. Słówko to zwraca wartość, którą wymienimy zaraz po nim. Jednocześnie warto pamiętać, że return automatycznie kończy wykonywanie funkcji. Wszystko, co będzie napisanie po nim, nie zostanie wykonane.

				
					function sum(x,y) {
    const c = x + y;
    return c;
}
				
			

Użycie zmiennej c nie jest tutaj koniecznie. Można użyć takiej zmiennej pomocniczej dla zapewnienia większej czytelności, jednak równie dobrze możemy napisać naszą funkcję bardziej skrótowo:

				
					function sum(x,y) {
    return x + y;
}
				
			

Jakiegokolwiek sposobu użyjemy, nasza funkcja teraz zwraca wynik. Oznacza to, że po wykonaniu obliczeń możemy go np. przypisać do zmiennej result i wykorzystać powiedzmy do wyświetlenia w pliku html.

				
					const result = sum(3,5);
				
			

Napiszmy teraz prostą funkcję printInHtml, która wyświetla nasz wynik w paragrafie o id „sum_result”.

				
					function printInHtml(sumResult) {
    const par = document.getElementById("sum_result");
    par.textContent = `${sumResult}`;
}
				
			

Mamy już wynik dodawania i możemy przekazać go do funkcji printInHtml.

				
					const result = sum(3,5);
printInHtml(result);
				
			

Podejście takie możemy zilustrować następująco:

JavaScript Callback - przekazywanie wyniku funkcji do innej funkcji

W całości nasz kod wygląda teraz następująco:

				
					// funkcja sumuje podane dwie liczby
function sum(x,y) {
    return x + y;
}

// funkcja wyświetla podaną wartość w html
function printInHtml(sumResult) {
    const par = document.getElementById("sum_result");
    par.textContent = `${sumResult}`;
}

// pobranie wyniku sumy i następnie przekazanie jej do funkcji wyświetlającej wartość w html
const result = sum(3,5);
printInHtml(result);
				
			

2. Jak już wyliczysz wynik to następnie zrób..

W drugim podejściu funkcję projektujemy w ten sposób, aby po obliczeniu sumy zrobiła kolejne czynności, czyli np. wywołała funkcję printInHtml. Być może dojdziemy do wniosku, że wyniku funkcji zawsze będziemy chcieli użyć do wyświetlenia w html. Po co więc mamy ręcznie przekazywać jej wynik do printInHtml, skoro możemy to od razu zlecić funkcji sum.

Kod funkcji sum mógłby więc wyglądać następująco:

				
					function sum(x,y) {
    const result = x + y;
    printInHtml(result);
}
				
			

W tym momencie funkcja sum nie tylko oblicza sumę, ale również za każdym razem sama wywołuje funkcję printInHtml. Oczywiście nazwa takiej funkcji powinna zostać odpowiednio zmodyfikowana, bo teraz nie tylko sumuje liczby, ale również dodaje wynik do html. Jednak dla uproszczenia zostawimy ją na razie tak, jak jest.

Aktualną sytuację możemy zilustrować następująco:

JavaScript Callback - wyjaśnienie zagnieżdżania funkcji

Takie podejście może wydawać się wygodne na pierwszy rzut oka. Wywołujemy funkcję sum podając jej tylko nasze liczby, a ona robi już wszystko co trzeba – oblicza wynik i wywołuje funkcję wyświetlającą wynik w html. 

Niestety w tym momencie możliwości wykorzystania naszej funkcji sum są już bardzo ograniczone. Nawet zakładając, że zawsze będziemy chcieli wyświetlić wynik w html, to możemy chcieć wywołać inną funkcję operującą na html, która np. doda nasz wynik do listy albo odpowiednio zmieni kolor w zależności od obliczonej wartości liczbowej.

Oczywiście przykład funkcji sumującej liczby jest bardzo uproszczony, ale mam nadzieję, że rozumiesz problem. W przypadku bardziej skomplikowanych działań, umieszczanie wewnątrz funkcji takich „dodatkowych” funkcjonalności jak np. wywoływanie jeszcze jakiejś innej funkcji może być problematyczne i ograniczać możliwości jej wykorzystania.

Poza tym powinniśmy bardzo uważnie wybrać nazwę takiej funkcji, aby nie wprowadzała nikogo w błąd. Nadanie nazwy funkcji sum, która oprócz sumowania liczb wywołuje jeszcze jakąś inną funkcję, będzie bardzo mylące.

Czy można jakoś rozwiązać ten problem?

Czy jesteśmy w takim razie skazani tylko na pierwsze podejście tzn. zwracanie wyniku funkcji i „ręczne” przekazywanie go dalej? Na szczęście nie. Język JavaScript pozwala na przekazywanie do funkcji jako parametru również innej funkcji. Możemy więc zaprojektować naszą funkcję tak, aby jako trzeci parametr przyjmowała nazwę innej funkcji. Następnie po obliczeniu wyniku, funkcja sum wywoła ten trzeci parametr i przekaże w wywołaniu wynik obliczeń.

				
					function sum(x,y,przekazana_funkcja) {
    const result = x + y;
    przekazana_funkcja(result);
}
				
			

Jak widzisz, funkcja sum, podobnie jak wcześniej, po obliczeniu wyniku wywołuje funkcję i przekazuje jej ten wynik. Jednak teraz używając funkcji sum mamy większą kontrolę nad tym, jaka dokładnie funkcja zostanie wywołana. Możemy użyć printInHtml, ale również możemy użyć dowolnie innej funkcji.

Teraz zamiast „na sztywno” umieszczać printInHtml w funkcji sum, przekażemy ją jako trzeci argument.

				
					sum(3,5,printInHtml);
				
			

Możemy zilustrować to następująco:

Wyjaśnienie jak działa callback w javascript

Teraz używając funkcji sum nie musimy się martwić o to, że wywołuje ona jakieś dodatkowe funkcje, których nie potrzebujemy. Po prostu przekazujemy jej jako argument funkcję, której wywołanie nas interesuje.

Właśnie taka funkcja, która jest przyjmowana przez inną funkcję jako parametr i później wywoływana, nazywana jest callbackiem

Wyjaśnienie jak działa callback w JavaScript

W jednej funkcji możemy oczywiście użyć zarówno callback jak i return, które sprawi, że funkcja będzie również zwracać wynik. Warto jednak zawsze dobrze przemyśleć strukturę funkcji, aby nie stworzyć sobie niepotrzebnego chaosu w programie.

To samo dotyczy przekazywania wielu callbacków do jednej funkcji, co też oczywiście możemy zrobić, oraz zagnieżdżania callbacków jeden w drugim.

Wiemy już, czym jest callback i jak można go wykorzystać do budowania funkcji. Funkcja sumująca liczby nie jest jednak najlepszym przykładem użycia callback. W tym wypadku akurat lepszym wyborem byłoby po prostu zwrócenie wyniku obliczeń używając słowa return. Przykładu tego użyliśmy tutaj po to, aby w jak najprostszy sposób pokazać działanie callback.

Znacznie lepszym i zbliżonym do „prawdziwego życia” przykładem jest użycie callback w połączeniu z tablicą. W kolejnym artykule zobaczmy, jak wykorzystać callback do operowania na tablicach i co ma do tego funkcja forEach.

Inne funkcje wykorzystujące callback

W języku JavaScript całkiem sporo wbudowanych funkcji wykorzystuje callback. Duża ich część to funkcje tablicowe jak foreEach()map(), filter(), every(), some() i wiele innych. Callback wykorzystywany jest też np. w funkcjach setTimeout() czy dodawaniu listenera do elementów DOM. Zachęcam Cię do zapoznania się z dokumentacją podanych przykładów. Znajdziesz tam znacznie więcej ciekawych przykładów użycia callback.

Jeszcze raz serdecznie zapraszam Cię też do artykułu na temat funkcji forEach, gdzie szerzej omawiamy użycie callbacków w połączeniu z tablicami.

Callbacki świetnie też nadają się do zadań asynchronicznych tzn. takich, które nie zostają zakończone od razu lecz dopiero po pewnym czasie. Np. pobieranie danych z serwera albo używanie funkcji setTimeout powoduje, że kod wewnątrz tych funkcji nie zostanie wykonany razem z resztą naszego kodu, lecz nieco później. W takich wypadkach zwykłe zwracanie wyniku z funkcji nic nam nie da, bo przeważnie otrzymamy undefined. Możemy zamiast tego użyć właśnie callback, który zostanie wywołany dopiero wtedy, kiedy dana funkcja faktycznie wykona jakieś działanie.

Podsumowanie

Przeszliśmy przez całą drogę budowania callbacku. Zobaczyliśmy, jakie są zalety takiego rozwiązania oraz omówiliśmy kilka przykładów jego zastosowania. Mam nadzieję, że pojęcie callbacku w JavaScript nie ma już dla Ciebie żadnych tajemnic i dokładnie wiesz, o co w tym chodzi.

Jeśli artykuł ten uważasz za wartościowy to zapraszam Cię również do subskrybcji newslettera. Dzięki temu dostaniesz informacje o nowych wpisach, jak tylko pojawią się one na blogu.

Wartościowe materiały prosto na Twoją skrzynkę mailową

Zapisz się do newslettera i nie przegap kolejnych artykułów, które pogłębią Twoją wiedzę o Java Script. Absolutne zero spamu.

Uzupełniając powyższe pole wyrażam zgodę na zapis do newslettera, aby otrzymać więcej materiałów i ciekawych linków z portalu javascriptbeztajemnic.pl. Zero spamu. Mogę w każdej chwili wycofać zgodę zgodnie z Polityką Prywatności.