Monolit-IT - Baza wiedzy

Baza wiedzy

JavaScript: async/await, Promise i Observable

08.04.2019

Od zarania dziejów języka JavaScript kod asynchroniczny był obsługiwany przez tzw. callbacki. Jak nieczytelny jest taki kod zapewne każdy programista wie. Na szczęście doczekaliśmy się bardzo eleganckiego rozwiązania.

Zacznijmy od historii słów kluczowych async/await. Zostały wprowadzone do EcmaScript w wersji 8 (rok 2017). Zgodnie z informacją podawaną przez stronę caniuse.com wszystkie liczące się przeglądarki mają te słowa zaimplementowane. A nawet jeśli okazałoby się, że wcześniejsza wersja przeglądarki tego nie wspiera, z pomocą przychodzi TypeScript, który identyczne słowa kluczowe udostępnia już od wersji 1.7 wydanej w 2015. Warto na marginesie odnotować, że async/await zostało jeszcze wcześniej dodane do języka C# i nie wydaje się przypadkiem, że ostatecznie trafiły do TypeScripta. A to dlatego, że jednym z projektantów tego języka jest niejaki Anders Hejlsberg, który jest również twórcą języka C#.

Tym oto sposobem dochodzimy do sedna sprawy. Dzięki await możemy pisać kod asynchroniczny w taki sposób, jakby wykonanie wyglądało na synchroniczne. Już wprowadzenie obietnic ograniczało callback hell, czyli wielokrotnie zagnieżdżone callbacki.

callbackHell1(function(result1) {
callbackHell2(function(result2) {
callbackHell3(function(result3) {
//etc….
});
});
});

Poniżej ten sam przykład z użyciem obietnic.

callbackHell1().then(function(result1) {
return callbackHell2();
}).then(function(result2) {
return callbackHell3();
}).then(function(result3) {
//etc…
});

Jak widać jest już znacznie lepiej, ale może być jeszcze lepiej. Zobaczmy, jak to będzie wyglądać, gdy użyjemy await.

const result1 = await callbackHell1();
const result2 = await callbackHell2();
const result3 = await callbackHell3();

No proszę, już lepiej chyba być nie może. Wszystko jest jasne i przejrzyste. Oczywiście należy pamiętać, że to wszystko jest tylko „syntaktycznym lukrem”, a pod spodem kod wykonuje się rzeczywiście asynchronicznie. Co najważniejsze słowa await możemy użyć wyłącznie w kontekście funkcji, która będzie oznaczona słowem kluczowym async.

async function asyncHeaven(){
return await callbackHell1();
}

I jeszcze jedna ważna rzecz: te słowa kluczowe współpracują wyłącznie z obietnicami. A więc await możemy postawić przed wywołaniem, które zwraca Promise, a funkcja oznaczona przez async musi zwrócić również Promise.

A teraz coś na temat „obserwabli”. Gdy ponad rok temu zawodowo zetknąłem się z Angularem, i to od razu w wersji 5, a wraz z nim z biblioteką rxjs, pomyślałem że Observable to nowy lepszy Promise. Zwłaszcza, że Angular promuje te pierwsze w nowym kliencie http. Wydawało mi się wówczas naturalne, żeby również w projekcie, w którym jestem zaangażowany, forsować stosowanie Observable . Chociażby dlatego, aby w całym projekcie w spójny sposób obsługiwać asynchroniczny kod. Z większym lub mniejszym powodzeniem się to udawało. Ale zacząłem dostrzegać też pewne niedogodności z tym związane.

Podstawową różnicą w działaniu Observable jest to, że jeżeli nie wywołamy na nim metody subscribe, wówczas asynchroniczna metoda się nie wykona. Przykład to np. http.get:

import { HttpClient } from '@angular/common/http';
@Injectable()
export class TestowyService {
constructor(private http: HttpClient) {}
public getConfig(): Observable < any > {
return this.http.get(‘jakiśurl’);
}
}

Jeżeli w powyższym przykładzie nie zasubskrybujemy wyniku metody, wówczas do serwera nie pójdzie żądanie GET. Może to czasami być zaletą, ale jeżeli do tej pory ktoś korzystał z obietnic, to może się zdziwić, bo nie mamy obowiązku wywoływać then.

Druga sprawa to fakt, że te obiekty nie są częścią języka, w związku z tym wprost nie są wspierane przez async/await. Aby to zrobić, musimy najpierw przekonwertować do obietnicy za pomocą metody toPromise.

Nie jest to wszystko szczególnie uciążliwe, ale pokazuje, że nie ma sensu na siłę korzystać z „obserwabli” jeżeli nie jest to koniecznie. Są oczywiście sytuacje, gdy obietnice nie znajdują zastosowania, a obiekty z biblioteki rxjs są wówczas niezastąpione, o czym kilka razy pisałem.

 

Zainteresował Cię ten wpis?
Chcesz dowiedzieć się więcej?

Michał Gierwatowski

Michał Gierwatowski

Programista wszechstronny, od języka Progress4GL począwszy, przez Javę, na TypeScripcie kończąc. Ponad piętnastoletnie doświadczenie w wytwarzaniu różnego rodzaju systemów informatycznych. Ostatnio interesuje się nowinkami w ekosystemie JavaScript/node.js

Michał.Gierwatowski(at)monolit-it.pl

Zobacz wszystkie artykuły danego autora »