Blog
Blog HelionBlog HelionBlog HelionBlog Helion
  • Artykuły
  • Autorzy
  • Recenzje
  • Konkursy

Obietnice w JavaScripcie – wprowadzenie

    Blog.helion.pl Artykuły Obietnice w JavaScripcie – wprowadzenie
    NastępnyPoprzedni

    Obietnice w JavaScripcie – wprowadzenie

    By Helion | Artykuły, Webmasterstwo | Brak komentarzy | 21 kwietnia, 2017 | 20

    Ostatnimi czasy coraz większą popularność w pracy z kodem asynchronicznym zdobywają tzw. obietnice. Pozostawiają większe pole do działania w zakresie kontroli synchronizacji, obsługi błędów i czytelności kodu, niż funkcje zwrotne.

    Czym są obietnice?

     

    Obietnice (ang. promise) to obiekty reprezentujące wartości, które będzie można wykorzystać w przyszłości. Przy ich użyciu można przechwycić wynik operacji asynchronicznej, np. zdarzenia, i obsłużyć ją w jednolity sposób. W odróżnieniu od technik obsługi zdarzeń bazujących na funkcjach zwrotnych obietnice gwarantują programiście otrzymanie wyników, nawet jeśli zdarzenie zachodzi, zanim zostanie zarejestrowana procedura jego obsługi (inaczej niż w przypadku zdarzeń, które mogą powodować wyścigi), oraz umożliwiają przechwytywanie i obsługiwanie wyjątków. Ponadto przy użyciu obietnic można pisać bardziej czytelny kod asynchroniczny.

    Obietnice są znane już od pewnego czasu i były używane przez programistów JavaScriptu w formie bibliotek. Ich definicję zawiera specyfikacja o nazwie Promise/A+ i zostały zaimplementowane w takich bibliotekach, jak Q, When.js czy RSVP.js i jQuery, a także są obsługiwane przez frameworki, np. AngularJS. Choć różne implementacje obietnic spełniają standardowe wymogi, ich interfejsy API różnią się między sobą.

    Podejmowano już kilka prób zdefiniowania jednolitej specyfikacji obietnic w języku JavaScript. Najbardziej znane zostały opublikowane przez społeczność CommonJS pod nazwami Promise/A, Promise/B i Promise/D. W standardzie ECMAScript 6 zaimplementowano specyfikację Promise/A+, którą można znaleźć pod adresem: https://promisesaplus.com/.

    Na szczęście w ECMAScripcie 6 obietnice wprowadzono jako obiekty macierzyste, dzięki czemu możemy korzystać z jednolitego API do obsługi kodu asynchronicznego, o czym przekonasz się w następnych sekcjach.

     

    Terminologia z zakresu obietnic

     

    Zanim przejdziemy do pisania kodu asynchronicznego przy użyciu obietnic, musimy poznać kilka pojęć, których będziemy często używać. Zaczniemy od stanów, w jakich obietnica może się znajdować:

    • Rozwiązana lub spełniona (ang. resolved lub fulfilled) — obietnica jest rozwiązana lub spełniona, gdy reprezentowana przez nią wartość jest dostępna, tzn. powiązane z nią zadanie asynchroniczne zwraca żądaną wartość.
    • Odrzucona (ang. rejected) — obietnica jest odrzucona, gdy związane z nią zadanie asynchroniczne nie zwraca wartości, np. z powodu wyjątku albo ponieważ zwrócona wartość jest nieprawidłowa.
    • Oczekująca (ang. pending) — w tym stanie obietnica pozostaje do czasu rozwiązania lub odrzucenia, tzn. od czasu wysłania żądania rozpoczęcia zadania asynchronicznego do otrzymania wyniku.

     

    Obietnica, która została rozwiązana lub odrzucona, staje się załatwiona (ang. settled).

    Ponieważ obietnica może zostać załatwiona tylko raz i obcy konsumenci nie mogą zmieniać jej stanu, obietnica jest niezmienna.

     

    Tworzenie obietnic

     

    Znając podstawową terminologię dotyczącą obietnic, możemy przejść do technik ich tworzenia oraz posługiwania się nimi w celu sprawniejszej obsługi kodu asynchronicznego. W przykładach używam składni ES6, ponieważ jest opisana w standardzie i można przewidywać, że w przyszłości wszystkie starsze biblioteki znikną lub się do niej dostosują.

    Obietnicę można utworzyć za pomocą konstruktora Promise():

    var promise = new Promise(handler);
    

    Konstruktor Promise() pobiera jako argument funkcję, której zadaniem jest stwierdzenie, czy dana obietnica została spełniona, czy też należy ją odrzucić. Typowa struktura takiej funkcji wygląda następująco:

    var promise = new Promise(function(resolve, reject) {
       if (condition) { // jakiś warunek
          resolve(value); // rozwiązanie obietnicy
       }  else {
          reject(reason); // odrzucenie obietnicy i podanie przyczyny
       }
    });
    

    W parametrach do funkcji obsługującej obietnicę przekazuje się dwie funkcje.

    Pierwsza z nich (w tym przykładzie nazwana resolve) to funkcja, która ma być wywoływana, gdy wartość zwrócona przez zadanie asynchroniczne stanie się dostępna. Ta zwrócona wartość zostaje przekazana do funkcji resolve.

    Drugi parametr (w tym przykładzie nazwany reject) jest funkcją, która ma zostać wywołana, gdy obietnica nie może zostać spełniona, np. z powodu błędu albo pojawienia się nieprawidłowej wartości. Kiedy obietnica zostanie odrzucona, do funkcji reject przekazywany jest powód (reason), np. wyjątek.

    W ramach konkretnego przykładu utworzenia obietnicy przebudujemy naszą starą funkcję httpGet():

    function httpGet(url) {
       return new Promise(function(resolve, reject) {
          var httpReq = new XMLHttpRequest();
    
          httpReq.onreadystatechange = function() {
             var data;
    
             if (httpReq.readyState == 4) {
                if (httpReq.status == 200) {
                   data = JSON.parse(httpReq.responseText);
                   resolve(data);
                }  else {
                   reject(new Error(httpReq.statusText));
                }
             }
          };
    
          httpReq.open("GET", url, true);
          httpReq.send();
        });
    }
    

    W tej wersji funkcja httpGet() pobiera tylko jeden argument — adres URL, który powinien odpowiedzieć na nasze żądanie HTTP. Funkcja ta zwraca obietnicę, której handler jest dość podobny do jej poprzedniej wersji. Tworzy żądanie HTTP przy użyciu obiektu XMLHttpRequest w normalny sposób, ale dodatkowo wywołuje funkcję resolve(), gdy otrzyma od serwera odpowiedź pomyślną, oraz reject(), gdy otrzyma odpowiedź niepomyślną. W pierwszym przypadku treść odpowiedzi jest przekazywana do funkcji resolve(), w drugim przekazywany jest wyjątek ze statusem HTTP do funkcji reject().

    Teraz funkcja httpGet() zwraca obietnicę zamiast bezpośrednio wysyłać żądanie HTTP do serwera.

     

    Używanie obietnic

     

    Ponieważ obietnica jest obiektem, można się nią posługiwać jak każdym obiektem, tzn. można ją zapisać w zmiennej, przekazać jako parametr, zwrócić przez funkcję itd. Na przykład obietnicę zwróconą przez funkcję httpGet() moglibyśmy zapisać w zmiennej, jak pokazano w poniższym kodzie:

    var myPromise = httpGet("/users/12345")
    

    Aby użyć obietnicy, tzn. wykorzystać reprezentowaną przez nią wartość, należy wywołać jej metodę then(). Do tej metody przekazuje się funkcję, która otrzyma wartość obietnicy w chwili, gdy ta stanie się dostępna.

    Spójrzmy na uproszczoną wersję poprzedniego przykładu. Za pomocą nowej funkcji httpGet() zbudowanej na podstawie obietnic pobieramy dane użytkownika i uzyskujemy dostęp do jego bloga. W tym celu posługujemy się poniższym kodem:

    httpGet("/users/12345")
       .then(function(user) {
          console.log("Identyfikator bloga użytkownika: " + user.blogId);
    });
    

    W tym kodzie przekazujemy do metody then() funkcję, której argument user zostanie związany z danymi asynchronicznie przesłanymi przez serwer. Kiedy dane staną się dostępne, obietnica będzie spełniona i nastąpi wywołanie funkcji.

    Aby wyświetlić listę wpisów na blogu użytkownika, możemy posłużyć się poniższym kodem:

    
    httpGet("/users/12345")
       .then(function(user) {
          httpGet("/blogs/" + user.blogId)
             .then(function(blog) {
                displayPostList(blog.posts);
             });
    });
    
    

    W tym przypadku funkcja konsumująca rozwiązaną obietnicę tworzy kolejną obietnicę w celu pobrania wpisów z bloga. W efekcie otrzymujemy zagnieżdżone obietnice, które zostaną rozwiązane po kolei, jedna po drugiej.

    Ostatecznie ten kod jest podobny do przedstawionego wcześniej rozwiązania z użyciem funkcji zwrotnych. Jednak wersja oparta na obietnicach zapewnia większą elastyczność i jak się wkrótce przekonasz, daje programiście szersze pole do działania.

    Zaczniemy od przepisania poprzedniego kodu w czytelniejszy sposób:

    function getUserData() {
       return httpGet("/users/12345");
    }
    function getBlog(user) {
       return httpGet("/blogs/" + user.blogId);
    }
    function displayBlog(blog) {
       displayPostList(blog.posts);
    }
    getUserData()
       .then(function(user) {
          getBlog(user)
             .then(function(blog) {
                displayPostList(blog.posts);
             })
    })
    

    Aby poprawić czytelność kodu, nadaliśmy funkcjom nazwy, ale to nie wszystko, co możemy zrobić.

    Metoda then() zawsze zwraca nową obietnicę, więc możemy stworzyć łańcuch wywołań zaznaczony pogrubieniem w poniższym przykładzie:

    function getUserData() {
       return httpGet("/users/12345");
    }
    function getBlog(user) {
       return httpGet("/blogs/" + user.blogId);
    }
    function displayBlog(blog) {
       displayPostList(blog.posts);
    }
    getUserData()
      .then(getBlog)
      .then(displayBlog);
    

    W tym przykładzie funkcje getUserData() i getBlog() zwracają obietnice utworzone przez funkcję httpGet(). Budowa takiego łańcucha wywołań była możliwa dzięki użyciu metody then() zwracającej te obietnice. Należy jednak wiedzieć, że metoda then() zawsze zwraca obietnicę, nawet gdy handler nie tworzy i nie zwraca jej bezpośrednio.

    Kiedy handler obietnicy nie zwraca obietnicy, tylko standardową wartość, np. podstawową albo obiekt, metoda then() tworzy nową obietnicę i rozwiązuje ją ze zwróconą wartością. Jeśli handler obietnicy nic nie zwraca, metoda then() i tak tworzy nową rozwiązaną obietnicę, którą zwraca.

     

    Przechwytywanie błędów

     

    W poprzednich przykładach wartość zwróconą przez obietnicę pobieraliśmy za pomocą metody then(). Jednak w razie gdyby coś się nie udało, obietnica może też zostać odrzucona. Należy wiedzieć, że odrzucenie obietnicy może nastąpić zarówno w wyniku odrzucenia bezpośredniego, jak i po wystąpieniu błędu w funkcji zwrotnej konstruktora. Przypadek odrzucenia obietnicy można obsłużyć, przekazując do metody then() drugą funkcję.

    W związku z tym nasz poprzedni kod mógłby wyglądać tak:

    function getUserData() {
       return httpGet("/users/12345");
    }
    function getBlog(user) {
       return httpGet("/blogs/" + user.blogId);
    }
    function displayBlog(blog) {
       displayPostList(blog.posts);
    }
    function manageError(error) {
       console.log(error.message);
    }
    getUserData()
       .then(getBlog, manageError)
       .then(displayBlog, manageError);
    
    

    Dodaliśmy funkcję manageError(), której zadaniem jest wyświetlanie powiadomienia o błędzie w konsoli. Funkcję tę przekazujemy jako drugi parametr do metody then() i zostanie ona wykonana w przypadku odrzucenia obietnicy.

    W tym przykładzie odrzucenie każdej obietnicy obsługujemy za pomocą tej samej funkcji, ale oczywiście nic nie stoi na przeszkodzie, aby dla każdej obietnicy utworzyć osobną funkcję. Wszystko zależy od tego, co jest potrzebne.

    Wiemy już, że drugi argument metody then() nie jest obowiązkowy. Pierwszy argument też jest opcjonalny. Można nawet w jego miejsce przekazać null, co oznacza, że interesuje nas tylko obsługa odrzuceń obietnicy. Możemy np. napisać taki kod:

    getUserData()
       .then(null, manageError);
    

    Ten kod zignoruje rozwiązaną obietnicę i będzie obsługiwał tylko błędy. W tym konkretnym przypadku takie rozwiązanie nie ma praktycznego zastosowania, ale za pomocą tej techniki można np. oddzielić obsługę spełnionych obietnic od ich odrzuceń, jak w poniższym przykładzie:

    getUserData()
       .then(getBlog)
       .then(null, manageError);
    

    Dzięki mechanizmowi łańcuchowego wywoływania metod odrzucona obietnica jest przekazywana od jednego wywołania metody then() do następnego. Kiedy dojdzie do odrzucenia obietnicy, gdy nie jest wyznaczona funkcja obsługująca odrzucenie, następuje utworzenie nowej obietnicy z takim samym powodem odrzucenia i przekazanie jej do następnej metody then() w celu obsłużenia. Dzięki takiemu sposobowi propagacji błędów, jeśli wszystkie błędy mają być obsługiwane w taki sam sposób, wystarczy utworzyć tylko jedną funkcję obsługi odrzuceń. W związku z tym poprzedni przykład możemy przepisać następująco:

    getUserData()
       .then(getBlog)
       .then(displayBlog)
       .then(null, manageError);
    

    W tym przypadku funkcja manageError() obsłuży wszystkie odrzucenia, jakie wystąpią w którymkolwiek miejscu tego łańcucha obietnic.

    Zamiast wartości null w pierwszym parametrze metody then() można użyć składni opartej na metodzie catch() obiektu obietnicy. Poniższy kod jest równoważny z poprzednim:

    getUserData()
       .then(getBlog)
       .then(displayBlog)
       .catch(manageError);
    

    Jak widać, używając obietnic, można tworzyć nie tylko czytelniejszy, ale i bardziej niezawodny kod, ponieważ zyskujemy możliwość przechwytywania i obsługiwania błędów asynchronicznych.

     

    Kompozycje obietnic

     

    Przedstawiony przykład użycia obietnic jest uproszczoną wersją przykładu zawartego w opisie techniki z użyciem funkcji zwrotnych. W tym przypadku skupiliśmy się tylko na pokazywaniu wpisów z bloga użytkownika, a pominęliśmy wyświetlanie zdjęć. Przypomnijmy sobie oryginalny kod:

    
    function getUserBlogAndPhoto(user) {
       getBlog(user.blogId);
       getPhotos(user.albumId);
    }
    function getBlog(blogId) {
       httpGet("/blogs/" + blogId, displayBlog);
    }
    function displayBlog(blog) {
       displayPostList(blog.posts);
    }
    function getPhotos(albumId) {
       httpGet("/photos/" + user.albumId, displayAlbum);
    }
    function displayAlbum(album) {
       displayPhotoList(album.photos);
    }
    
    httpGet("/users/12345", getUserBlogAndPhoto);
    

    Wykorzystując zdobytą wiedzę o obietnicach, możemy przepisać ten kod w następujący sposób:

    function getUserData() {
       return httpGet("/users/12345");
    }
    function getBlog(user) {
       return httpGet("/blogs/" + user.blogId);
    }
    function displayBlog(blog) {
       displayPostList(blog.posts);
    }
    function getPhotos(user) {
       return httpGet("/photos/" + user.albumId);
    }
    function displayAlbum(album) {
       displayPhotoList(album.photos);
    }
    function manageError(error) {
       console.log(error.message);
    }
    function getBlogAndPhotos(user) {
       getBlog(user)
          .then(displayBlog);
    
    getPhotos(user)
       .then(displayAlbum);
    }
    
    getUserData()
       .then(getBlogAndPhotos)
       .catch(manageError);
    

    W tym przypadku połączyliśmy poprzedni kod oparty na obietnicach z nowym wywołaniem HTTP mającym na celu pobranie zdjęć użytkownika. Do obsługi dwóch asynchronicznych zadań dodaliśmy funkcję getBlogAndPhotos(), która pobiera zarówno wpisy, jak i zdjęcia. Ale w jaki sposób w tym przypadku będą obsługiwane obietnice? Co się stanie, gdy jedna z nich zostanie rozwiązana, a druga odrzucona?

    Jeśli obie obietnice utworzone w funkcji getBlogAndPhotos() zostaną rozwiązane, na stronie wyświetlą się wpisy i zdjęcia. Nie wiadomo, jaka będzie ich kolejność — będą wyświetlane według porządku otrzymywania i rozwiązywania obietnic.

    Jednak przeciwnie do tego, czego możesz się spodziewać, jeśli przynajmniej jedna z obietnic zostanie odrzucona, nie zostanie ona obsłużona w planowany przez nas sposób. Funkcja manageError() nie zostanie wywołana. Zgodnie z tym, co napisałem na temat mechanizmu łączenia w łańcuchy wywołań metody then(), jeśli funkcja obsługi rozwiązanej obietnicy nie zwróci niczego, tak jak w naszym przypadku, nastąpi utworzenie nowej rozwiązanej obietnicy, która zostanie przekazana do następnej funkcji obsługowej w łańcuchu. Kiedy więc zostanie wykonana funkcja getBlogAndPhotos(), przekaże ona niejawnie rozwiązaną obietnicę do następnej funkcji obsługowej. Ponieważ na końcu wywołujemy metodę catch(), rozwiązana obietnica zostanie zignorowana i łańcuch obietnic będzie uznany za rozwiązany. Gdy jedna lub obie obietnice utworzone przez funkcje getBlog() i getPhotos() zostaną odrzucone, nie będą już mogły przekazać obiektu do metody catch(), ponieważ nie będzie ona już czekała na ich załatwienie.

    Gdy potrzebny jest wynik mnogiego zadania asynchronicznego, należy użyć metody all() konstruktora Promise. Metoda ta może poczekać na rozwiązanie wszystkich powiązanych obietnic. Możemy więc zsynchronizować wszystkie asynchroniczne zadania i obsłużyć wyniki ich wszystkich jednocześnie. Metoda Promise.all() przyjmuje tablicę obietnic i tworzy obietnicę, która jest rozwiązana, gdy wszystkie znajdujące się w tablicy obietnice są spełnione. Gdy wszystkie obietnice w tablicy są spełnione, do funkcji obsługowej zostaje przekazana tablica z otrzymanymi wartościami. Jeżeli którakolwiek z obietnic zostanie odrzucona, następuje odrzucenie całej tablicy obietnic i wywołanie metody catch().

    W związku z tym dzięki metodzie Promise.all() możemy poczekać z wyświetlaniem treści, aż wszystkie wpisy i zdjęcia staną się dostępne. To z kolei daje nam możliwość określenia kolejności wyświetlania elementów treści i podjęcia odpowiednich działań w przypadku odrzuceń. Nasz poprzedni kod możemy zatem przepisać w następujący sposób:

    
    getUserData()
       .then(function(user) {
          var promises = [];
          var blog = getBlog(user);
          var album = getAlbum(user);
    
          promises.push(blog);
          promises.push(photos);
    
          Promise.all(promises)
          .then(function(results) {
             displayBlog(results[0]);
          displayAlbum(results[1]);
       })
    })
    .catch(manageError);
    
    

    Zdefiniowaliśmy tablicę promises i wstawiliśmy do niej obietnice utworzone przez wywołania funkcji getBlog() i getAlbum(). Następnie tablicę tę przekazujemy do metody Promise.all(), która utworzy dla nas nową obietnicę. Kiedy obie obietnice zostaną rozwiązane, wyświetlimy dane zwrócone przez serwer w takiej kolejności, w jakiej będziemy chcieli. W przykładzie najpierw prezentujemy wpisy, a potem zdjęcia. Jeśli któraś z obietnic zostanie odrzucona, zgodnie z oczekiwaniami będzie wykonana metoda catch().

    Innym możliwym sposobem pracy z wieloma zadaniami asynchronicznymi jest użycie metody Promise.race(). Podobnie jak Promise.all() metoda ta pobiera tablicę obietnic i tworzy nową obietnicę. Różni się od niej tym, że rozwiązuje utworzoną obietnicę, gdy którakolwiek z obietnic znajdujących się w tablicy zostanie rozwiązana.

    Przy użyciu tej techniki można wyświetlić pierwszą treść, która stanie się dostępna po wysłaniu żądań wpisów i zdjęć do serwera. Poniżej znajduje się jej przykładowa realizacja w postaci kodu źródłowego:

    Promise.race(promises)
       .then(function(result) {
          if (result.posts) displayBlog(results);
          if (result.photos) displayAlbum(results);
    })
    .catch(manageError);
    
    

    Obietnica związana z tablicą promises zostanie rozwiązana w chwili wpłynięcia wpisów z bloga lub zdjęć. W funkcji obsługującej sprawdzamy, jaka treść dotarła, i wywołujemy odpowiednią funkcję do jej wyświetlenia.

     


    Tekst pochodzi z książki „Mistrzowski JavaScript. Programowanie zorientowane obiektowo” (Andrea Chiarelli) – Wyd. Helion 2017.

    Sprawdź książkę >>

    Sprawdź eBook >>

     

    frontend, Javascript, książka, obietnice, programowanie, programowanie asynchroniczne, webmasterstwo
    Avatar

    Helion

    Więcej postów od Helion

    Podobne posty

    • Ta książka tak naprawdę nie jest o języku C [FRAGMENT]

      By Helion | Brak komentarzy

      Proszę, nie czuj się oszukany, gdy powiem, że ta książka nie ma na celu nauczenia Cię programowania w języku C. Wprawdzie dowiesz się, jak tworzyć programy w C, ale najważniejsza umiejętność, jaką możesz zdobyć podczasCzytaj więcej…

    • Błyskawiczny kurs pisania skryptów powłoki

      By Helion | Brak komentarzy

      Powłoka bash i skrypty powłoki istnieją już od długiego czasu, a każdego dnia nowi użytkownicy poznają ich siłę i możliwości automatyzowania operacji systemowych. A od kiedy firma Microsoft opracowała dla swojego systemu Windows 10 interaktywnąCzytaj więcej…

    • Przykładowa aplikacja webowa zaimplementowana w ASP .NET Core

      By Krzysztof Goljasz | Brak komentarzy

      W poprzednim artykule zainstalowaliśmy oraz stworzyliśmy pierwszą aplikację webową z wykorzystaniem frameworka .NET Core. Obecnie zajmiemy się zaimplementowaniem prostej aplikacji CRUD pracującej na silniku MS SQL Server zainstalowanym w systemie Linux.

    • Wprowadzenie do .NET Core: instalacja, konfiguracja, pierwsza aplikacja w systemie Linux

      By Krzysztof Goljasz | 1 komentarz

      W ostatnim czasie Microsoft wprowadził na rynek nowy framework o tajemniczej nazwie .NET Core, który jest na etapie intensywnego rozwoju (obecna wersja — 1.1.1). Czym jest .NET Core? To nowa wersja klasycznego frameworka .NET zCzytaj więcej…

    • Nie bój się refaktoryzacji!

      By Jerzy Piechowiak | Brak komentarzy

      Bardzo często w tytułach książek dla programistów można znaleźć określenia typu: „piękny kod”, „czysty kod”, „kod doskonały”, sugerujące, że możliwe jest napisanie programu, którego kod źródłowy jest po prostu idealny. Ale co właściwie znaczy to,Czytaj więcej…

    NastępnyPoprzedni

    Znajdź post

    Bądźmy w kontakcie

    Książka dnia

    Algorytmy, struktury danych i techniki programowania dla programistów Java

    Autor: Piotr Wróblewski

    Cena: 33.50 zł 67.00 zł
    (Cena e-booka: zł zł)

    O 33,5zł taniej!

    kup teraz

    Ostatnie wpisy

    • Błyskawiczny kurs pisania skryptów powłoki
    • Przykładowa aplikacja webowa zaimplementowana w ASP .NET Core
    • Wprowadzenie do .NET Core: instalacja, konfiguracja, pierwsza aplikacja w systemie Linux
    • Grupa Helion zaprasza na szkolenia stacjonarne!
    • Hello World! Czym jest programowanie?

    Tagi

    .net agile altcontroldelete asp.net c# czysty kod debugowanie design patterns e-biznes emarketing Google Google Analytics hacking Jerzy Piechowiak kod kodowanie Krzysztof Marzec książka Maciej Dutko magazyn programista Magdalena Daniłoś marketing MVVM onepress organizacja czasu praca prograista programista programowanie prokrastynacja rafał kocisz reklama rozwój software craftsman SOLID startup techniki programowania testowanie video marketing visual studio WPF wzorce projektowe youtube zarządzanie czasem zarządzanie projektami

    Archiwum

    • lipiec 2017
    • czerwiec 2017
    • maj 2017
    • kwiecień 2017
    • marzec 2017
    • luty 2017
    • styczeń 2017
    • grudzień 2016
    • listopad 2016
    • październik 2016
    • wrzesień 2016
    • lipiec 2016
    • czerwiec 2016
    Blog wydawnictwa
    Informatyka w najlepszym wydaniu
    Strona wydawcy:
    www.helion.pl
    Księgarnia Helion.pl
    Nowości
    Bestsellery
    Promocje
    Bądźmy w kontakcie:
    Chcesz zostać autorem?
    Masz pytania do redakcji?
    Napisz do nas »
    • Artykuły
    • Autorzy
    • Recenzje
    • Konkursy
    Blog Helion