Dzisiejszy wpis, jak można się domyślić po przeczytaniu jego tytułu, ponownie poświęcony będzie językowi JavaScript, a konkretniej wykorzystaniu i implementacji funkcji typu “callback”. Dowiesz się z niego co to jest funkcja callback, jak i gdzie ją stosować i wiele innych ważnych informacji. Jak zwykle, post traktować będzie o zagadnieniach podanych na stronach Microsoftu, dotyczących właśnie tego celu egzaminacyjnego. A zatem, poznamy dziś zagadnienie odbierania wiadomości z HTML5 WebSocket API; dowiemy się w jaki sposób użyć jQuery (stąd obrazek towarzyszący nagłówkowi posta ;)) do wykonania wywołania asynchronicznego (AJAX); omówimy także sposób na implementację własnych “callback’ów”; dowiemy się również, w jaki sposób wykorzystuje się słowo kluczowe “this” w tego rodzaju funkcjach. To wszystko tytułem wstępu, przejdźmy do rzeczy!

HTML5 WebSocket API

W ramach celu egzaminacyjnego dotyczącego implementacji “callback’ów”, Microsoft nakazuje zapoznać sie również, z kwestią odbierania wiadomości poprzez ‘HTML5 WebSocket API’. Myślę, że nie będę tutaj zagłębiać się we wszystkie możliwości tego API. Zamiast tego skupię się tylko na wyjaśnieniu o co chodzi i pokazaniu jak nawiązać połączenie i jak odebrać wiadomość (zgodnie z tym czego oczekuje się na egzaminie).

A więc do rzeczy, specyfikacja ‘HTML5 WebSockets’ definiuje API, które umożliwia stronom internetowym komunikację ze zdalnym hostem, za pomocą protokołu ‘WebSockets’. Wprowadza on interfejs dla ‘WebSocket’ów’ i definiuje kanał do transmisji dwukierunkowej (ang. full-duplex). Tyle suchej definicji - więcej szczegółów można znaleźć na przykład na stronach websocket.org.

Przykład

Przejdźmy zatem do przykładu komunikacji za pomocą ‘HTML5 WebSocket API’. Na początek utworzyć należy instancję interfejsu WebSocket, tak jak w poniższym przykładzie:

var socket = new WebSocket("ws://www.burczu.websockets.org/echo");

Narazie sprawa wygląda prosto. Tworzymy po prostu obiekt ‘socket’, podając w konstruktorze klasy ‘WebSocket’ adres URL serwera, z którym chcemy się połączyć. Warto zauważyć, że jako prefixu protokołu, użyto wartości “ws”. Dostępna jest także opcja “wss” czyli bezpieczne połączenie (na tej samej zasadzie jak ‘http’ oraz ‘https’).

Utworzenie obiektu ‘socket’ powoduje jednocześnie próbę nawiązania połączenia z hostem dostępnym pod adresem podanym w konstruktorze. Obiekt ten udostępnia programiście, kilka zdarzeń, takich jak ‘onopen’, ‘onerror’ czy ‘onmessage’, dzięki którym możemy reagować na otwarcie połączenia, wystąpienie błędu lub odebranie wiadomości. Spójrzmy na przykład, który to pokazuje:

socket.onopen = function () {
  // kiedy polaczenie zostanie otwarte, wyslij wiadomosc
  socket.send('hello');
};

socket.onerror = function (error) {
  // reagujemy na bledy
  alert('blad ' + error);
};

socket.onmessage = function (e) {
  // odbieramy odpowiedz
  alert('odpowiedz serwera: ' + e.data);

  // zamykamy polaczenie
  socket.close();
};

socket.onclose = function(e) {
  // reagujemy na zamkniecie polaczenia
  alert("polaczenie zakonczone!");
};

Przykład powyższy pokazuje w zasadzie wszystkie użyteczne zdarzenia obiektu ‘socket’. Możemy też zauważyć jak łatwo wysłać jest wiadomość do zdalnego serwera (metoda ‘send’ z treścią wiadomości jako parametr), a także jak można ją odebrać (poprzez obsłużenie zdarzenia ‘onmessage’, które ma miejsce kiedy odebrana zostanie wiadomość, a jej treść znajduje się w obiekcie zdarzenia przekazanym do funkcji obsługującej).

Przesyłanie danych innych niż ciąg znaków

W zasadzie można by już na tym zakończyć, jednak możliwe jest również przesyłanie do serwera danych innych niż ciągi znaków - można przesłać również dane w postaci binarnej lub pliki. Poniżej przykład pokazujący jak to zrobić (przykład, ze zmianami pochodzi ze strony html5rocks.com):

socket.send('wiadomosc jako ciag znakow'); // zwykły string

// przesylanie obrazka binarnie (jako 'ArrayBuffer')
var img = canvas.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
socket.send(binary.buffer); // wysylamy dane jako 'ArrayBuffer'

// przesylanie pliku
var file = document.querySelector('input[type="file"]').files[0];
socket.send(file); // przesylamy plik jako 'Blob'

// odbieranie
socket.binaryType = 'arraybuffer'; // informujemy ze spodziewamy sie danych typu 'ArrayBuffer'
socket.onmessage = function(e) {
  alert(e.data.byteLength); // w e.data mamy dane typu 'ArrayBuffer'
};

Jak widać, w przypadku wysyłania danych wystarczy podać dane odpowiedniego typu jako parametr metody ‘send’. Więcej zachodu jest w przypadku odbierania danych, ponieważ zanim odbierze się dane, należy za pomocą właściwości ‘binaryType’, ustawić jaki typ danych binarnych zostanie przesłany (domyślnie jest to ‘blob’) - oczywiście jeśli spodziewamy się po prostu ciągu znaków, niczego nie trzeba ustawiać.

Wywołania asynchroniczne za pomocą jQuery

Dla porządku, na początek definicja: AJAX to skrót od “Asynchronous JavaScript And XML” - jest to zbiór technik stosowanych po stronie klienta (przeglądarki) do tworzenia asynchronicznych aplikacji, tzn. takich, które wykonują część (lub wszystkie) operacji, odwołując się do serwera w tle, bez przeładowania okna przeglądarki (tzn. zawartość i wygląd strony może się zmieniać bez przeładowania).

Skoro część definicyjną mamy już za sobą, przejdźmy do konkretów. Generalnie mamy pięć głównych sposobów na wykonywanie wywołań asynchronicznych w jQuery. Poniżej opis każdego z tych sposobów.

Metoda “load” - pobieranie kodu HTML ze zdalnego hosta i wstrzykiwanie go do dokumentu DOM

Jednym z najczęstszych sposobów wykorzystania AJAX’a jest asynchroniczne pobranie kodu HTML ze zdalnego serwera i wstrzyknięcie go do dokumentu DOM. Do tego celu wykorzystać można funkcję “load” dostępną w jQuery w kontekście obiektów DOM. Najlepiej spójrzmy na przykład:

var action = "home/gethtml"; // akcja która zwroci kod html do wstrzykniecia

$("#button").click(function(){
    // do "wnętrza" elementu 'container' ładujemy wynik akcji 'home/gethtml'
    $("#container").load(action);
});

W powyższym przykładzie, dla przycisku o identyfikatorze ‘button’ mamy zdefiniowaną funkcję obsługującą zdarzenie kliknięcia - w jej wnętrzu, w kontekście elementu o identyfikatorze ‘container’ (przyjmijmy że jest to element <div>), wywoływana jest funkcja ‘load’, do której jako parametr przekazujemy adres akcji na serwerze, która zwrócić ma kod HTML. Kod ten jest następnie wstrzykiwany do naszego kontenera. Prawda, że proste? ;)

Ładowanie tylko części HTML

Mało tego, możliwe jest również wstrzyknięcie tylko części dokumentu HTML pobranego z serwera. Przyjmijmy, że akcja ‘home/gethtml’, z poprzedniego przykłady zwraca taki oto kod HTML:

<div id="indexOne">
  <p>Some text in first container</p>
</div>
<div id="indexTwo">
  <p>Some text in second container</p>
</div>

Teraz, jeśli chcielibyśmy aby do naszego kontenera wstrzyknięty został tylko element <div> o indeksie ‘‘indexTwo”, wówczas moglibyśmy wywołać metodę ‘load’ w taki sposób:

$("#container").load(action + '#indexTwo');

Przekazywanie parametrów za pomocą GET

Kolejna sprawa, to przekazywanie parametrów do serwera. Na początek weźmy metodę GET. Tutaj sprawa wygląda prosto, ponieważ wystarczy, że ciąg parametrów podamy jako drugi parametr metody ‘load’:

var action = "home/gethtml"; // akcja która zwroci kod html do wstrzykniecia

$("#button").click(function(){
    // do "wnętrza" elementu 'container' ładujemy wynik akcji 'home/gethtml'
    $("#container").load(action, 'firstParam=0&secondParam=1');
});

Jak widać, ciąg parametrów przekazujemy w sposób standardowy, poszczególne parametry oddzielając znakiem ‘&’.

Przekazywanie parametrów za pomocą POST

Możliwe jest oczywiście przekazywanie parametrów, również z pomocą metody POST. Sposób jest dość podobny do powyższego, spójrzmy na przykład:

var action = "home/gethtml"; // akcja która zwroci kod html do wstrzykniecia

$("#button").click(function(){
    // do "wnętrza" elementu 'container' ładujemy wynik akcji 'home/gethtml'
    $("#container").load(action, { firstParam = 0, secondParam = 1 });
});

Z powyższego wynika, że jeśli chcemy przekazać parametry metodą POST, przekazujemy je jako obiekt, którego poszczególne właściwości to poszczególne parametry.

Reakcja na ładowanie zakończone sukcesem

Funkcja ‘load’, może przyjmować jeszcze trzeci parametr. Rozpocznijmy od przykładu:

var action = "home/gethtml"; // akcja która zwroci kod html do wstrzykniecia

$("#button").click(function(){
    // do "wnętrza" elementu 'container' ładujemy wynik akcji 'home/gethtml'
    $("#container").load(action, null, function() {
        alert('sukces! ladowanie udane!');
    });
});

Po pierwsze widzimy, że jako drugi parametr przekazujemy wartość ‘null’ - robimy tak jeśli nie chcemy przekazywać parametrów do akcji, a jednocześnie chcemy przekazać trzeci parametr do metody ‘load’. Jak widzimy, trzecim parametrem jest funkcja anonimowa - zostanie ona uruchomiona jeśli ładowanie kodu HTML ze zdalnego serwera zakończy się powodzeniem.

Co to jest funkcja callback?

Tym sposobem dobrnęliśmy do głównego tematu tego wpisu - funkcja przekazana jako parametr innej funkcji to właśnie funkcja “callback”. Dzięki takiemu mechanizmowi, możliwe jest definiowanie jakie działania mają zostać podjęte, w zależności od wyniku działania danej funkcji (w powyższym przykładzie, funkcja “callback”, którą przekazujemy do funkcji ‘load’ wykonana zostanie tylko jeśli operacja ładowania kodu HTML się powiedzie.

Do tego tematu jeszcze wrócimy, na razie pozostańmy jednak przy wywołaniach asynchronicznych w jQuery.

Metoda “getJSON” - pobieranie danych w formacie JSON

Przyszedł czas na kolejną metodę służącą do wywołań AJAX’owych w jQuery. Na początek krótka definicja, dla porządku (za wikipedia.org):

JSON, JavaScript Object Notation (wym. ˈdʒeɪsən) – lekki format wymiany danych komputerowych. JSON jest formatem tekstowym, będącym podzbiorem języka JavaScript. Typ MIME dla formatu JSON to application/json. Format został opisany w dokumencie RFC 4627.

Wykorzystanie JSON’a do wywołań asynchronicznych jest bardzo wygodne i bardzo często stosowane, ponieważ zajmuje on mniej miejsca niż standardowy XML.

Spójrzmy więc na przykład użycia tej metody:

var action = 'home/getJSONdata';

$.getJSON(action, function(data) {
    // iterujemy po zwróconych danych
    $.each(data, function(key, val) {
        alert('klucz: ' + key + ', wartość: ' + val)
    });
});

Powyższy przykład pokazuje, że metoda getJSON, również wykorzystuje mechanizm funkcji “callback”. W przykładzie, jako pierwszy parametr funkcji podajemy adres URL akcji, która zwróci nam dane w formacie JSON. Drugim parametrem jest funkcja “callback”, która wykonana zostanie kiedy dane zostaną pobrane (jako parametr, przekazywane są do niej pobrane dane).

W ciele tej funkcji, wykonujemy iterację po elementach zawartych w parametrze wejściowym ‘data’ - do tego celu użyta została metoda ‘each’ pochodząca z biblioteki jQuery. Jak widać, ona również wykorzystuje mechanizm “callback’ów” - jako drugi parametr podawana jest funkcja, która uruchamiana jest za każdym przebiegiem pętli. Jako jej parametry, przekazywane są do niej klucz i wartość, dzięki czemu mamy w ciele funkcji dostęp do tych danych.

Przykład formatu JSON

Dla rozjaśnienia, dane w formacie JSON przekazywane są jako pary “klucz - wartość” - wygląda to mniej więcej tak:

{
  "jeden": "Pierwsza wartość",
  "dwa": "Druga wartość",
  "trzy": "Trzecia wartość"
}

Oczywiście dane w tym formacie mogą mieć bardziej skomplikowaną strukturę, być bardziej zagnieżdżone, są bowiem odpowiednikiem danych zapisanych w formacie XML.

Parametry wywołania metody “getJSON” poniżej (w nawiasach kwadratowych parametry opcjonalne):

  • url - adres URL, pod którym oczekuje się zwrócenia danych w formacie JSON
  • [data] - obiekt lub tekst przesyłany do serwera; tak jak w przypadku metody ‘load’, przekazuje się w ten sposób parametry metodą POST lub GET
  • [callback] - funkcja “callback” wywoływana w przypadku powodzenia pobierania danych

Metoda “getScript” - pobieranie skryptu JavaScript ze zdalnej lokacji

Istnieje też możliwość asynchronicznego pobrania całego skryptu JS, ze zdalnego serwera w celu wstrzyknięcia go do dokumentu HTML. Spójrzmy na prosty przykład:

var scriptUrl = "scripts/someScript.js";

$("#button").click(function(){
    // pobranie i uruchomienie skryptu
    $.getScript(scriptUrl, function(){
        $("#container").html("<p>skrypt załadowany!</p>");
    });
});

Metoda ‘getScript’ również podobna jest do metody ‘load’, z tą różnicą że przyjmuje tylko dwa parametry: adres URL skryptu (lub akcji, która zwraca skrypt) oraz funkcję “callback”, uruchamianą gdy pobranie skryptu zakończy się powodzeniem. Należy zauważyć, że nie ma możliwości przekazania dodatkowych parametrów wywołania adresu URL metodą POST lub GET (oczywiście można dokleić odpowiedni “query string” do adresu URL, w efekcie przekazując parametry metodą GET).

Metoda “get” - wywołania typu GET

Metoda ‘get’, jak sama nazwa wskazuje, służy do wywołań asynchronicznych typu GET. Jej siłą jest to, że pozwala na przyjmowanie odpowiedzi dowolnego typu (zarówno zwykły tekst czy HTML, jak i JSON lub JavaScript). Poniżej przykład jej wywołania:

var action = 'home/getdata';

$("#button").click(function(){
    $.get(
        action,
        {
            firstParam: 0,
            secondParam: 1
        },
        function(responseText) {
            $("#container").html(responseText);
        },
        "html" // typ odpowiedzi, mógłby to być też np. "json" itp.
    );
});

Przykład pokazuje, że znów jako pierwszy parametr przekazywany jest adres URL akcji, która zwrócić ma dane. Kolejny parametr, to parametry wywołania, które przesłane zostaną metodą GET - w odróżnieniu od metody ‘load’, tym razem one również przekazywane są jako obiekt a nie jako tekst. Trzeci parametr to jak zwykle funkcja “callback” wywoływana w przypadku gdy pobieranie danych zakończy się powodzeniem. Ostatni parametr to informacja, jakiego typu danych spodziewamy się, że będzie zwrócony przez akcję podaną w pierwszym parametrze (wartości jakie można podać to: ‘xml’, ‘json’, ‘script’ lub ‘html’ - jeśli nie podamy nastąpi automatyczna próba rozpoznania typu).

Metoda “post” - wywołania typu POST

W przypadku metody ‘post’, mamy do czynienia z analogią do metody ‘get’, z tą różnicą, że parametry wywołania adresu URL zostaną przesłane metodą POST. Dla porządku spójrzmy na przykład:

var action = 'home/getdata';

$("#button").click(function(){
    $.post(
        action,
        {
            firstParam: 0,
            secondParam: 1
        },
        function(responseText){
            $("#container").html(responseText);
        },
        "html" // typ odpowiedzi, mógłby to być też np. "json" itp.
    );
});

Jak widać, są to dokładnie te same operacje jak w poprzednim przykładzie, z tą różnicą, że wywołanie wykonywane jest metodą POST a nie GET.

Na koniec… metoda “ajax”

Opisane powyżej funkcje powinny wystarczyć do większość “codziennych” potrzeb. Jednak tak na prawdę, każda z nich “pod spodem” wywołuje metodę ‘ajax’. Czasami, szczególnie kiedy potrzebna nam jest implementacja metody “callback” dla przypadku kiedy coś pójdzie nie tak, można skorzystać z metody ‘ajax’ bezpośrednio.

Z racji tego, że metoda ta może przyjmować bardzo wiele dodatkowych ustawień, nie będę się w tym artykule w nią zagłębiał - zamiast tego zapraszam do opisu metody ‘ajax’ w oficjalnej dokumentacji jQuery.

Implementacja metody “callback” oraz słowo kluczowe “this”

W zasadzie po przeczytaniu poprzedniego paragrafu, na temat wywołań asynchronicznych w jQuery, zagadnienie funkcji “callback” powinno być już w miarę jasne. Jednak dla porządku postanowiłem poświęcić temu zagadnieniu osobny paragraf.

Przykład funkcji callback

Jak już wcześniej napisałem, funkcja “callback” to funkcja, którą przekazuje się jako parametr do innej funkcji, dzięki czemu uzyskujemy możliwość oddelegowania reakcji na wynik działania tej innej funkcji na zewnątrz. Spójrzmy na przykład:

// pewna funkcja
function someFunction(x, y, callback) {
    if (x + y > 10) {
        callback();
    }
}

// implementacja funkcje callback
function callbackFunction() {
    alert('funkcja callback!');
}

// wywołanie pewnej funkcji razem z callbackiem
someFunction(5, 8, callbackFunction);

Ten prosty przykład pokazuje dokładnie mechanizm “callback’ów”. Na początku mamy definicję funkcji, przyjmującej trzy parametry. Trzecim z nich jest funkcja, która wywoływana jest, kiedy suma dwóch pierwszych parametrów jest większa od 10. W ten sposób oddelegowujemy reakcję na sytuację, kiedy nasze obliczenia osiągną konkretny wynik, do funkcji, która jest zdefiniowana “na zewnątrz”. Na końcu widzimy wywołanie funkcji i sposób na przekazanie funkcji “callback” - przekazujemy do po prostu jej nazwę.

Użycie funkcji anonimowej

Innym, często stosowanym sposobem wywołania funkcji przyjmującej “callback” jako parametr, jest zastosowanie funkcji anonimowej. Ten sam przykład co powyżej, tylko z zastosowaniem funkcji anonimowej:

// pewna funkcja
function someFunction(x, y, callback) {
    if (x + y > 10) {
        callback();
    }
}

// wywołanie pewnej funkcji razem z anonimowym callbackiem
someFunction(5, 8, function() {
    alert('funkcja callback!');
});

Wykorzystanie funkcji call

Istnieje również, alternatywny sposób wywołania funkcji “callback” wewnątrz funkcji ‘someFunction’ z powyższego przykładu. Można mianowicie, w kontekście parametru “callback” użyć metody ‘call’. Dzięki takiemu wywołaniu, możemy ustawić kontekst wobec, którego “callback” jest wywoływany. Spójrzmy na przykład:

// pewna funkcja
function someFunction(x, y, callback) {
    var test = 'wartosc testowa';
    if (x + y > 10) {
        callback.call(test);
    }
}

// wywołanie pewnej funkcji razem z anonimowym callbackiem
someFunction(5, 8, function() {
    alert('funkcja callback! this: ' + this);
});

W funkcji ‘someFunction’ pojawia się dodatkowa zmienna ‘test’. Zmienną tą przekazujemy następnie jako parametr wywołania metody ‘call’ - linia piąta. Dzięki temu ustawiamy kontekst wywołania funkcji “callback” na tę właśnie zmienną, dzięki czemu w funkcji, którą przekazujemy jako parametr podczas wywołania funkcji ‘someFunction’, mamy dostęp do wartości zmiennej ‘test’ poprzez słowo kluczowe ‘this’.

Co to jest funkcja callback - podsumowanie

To wszystko co na dziś przygotowałem. Mam nadzieję, że wyczerpuje to temat “implementacja callback’ów” na egzaminie 70-480 ;) Oprócz tego mam nadzieję, że będziesz już teraz pamiętał co to jest funkcja callback w JavaScript… Zapraszam również na kolejną wpis, gdzie postaram się omówić temat tworzenia procesu “web workera”.