Co to jest i jak z niego korzystać

Podczas pisania aplikacji JavaScript mogłeś napotkać funkcje asynchroniczne, takie jak funkcja fetch w przeglądarce lub funkcja readFile w Nodejs.

Być może uzyskałeś nieoczekiwane wyniki, jeśli użyłeś jednej z tych funkcji w normalny sposób. Dzieje się tak, ponieważ są to funkcje asynchroniczne. Ten artykuł wyjaśnia, co to oznacza i jak używać funkcji asynchronicznych jak profesjonalista.

Wprowadzenie do funkcji synchronicznej

JavaScript to język jednowątkowy, który może wykonywać tylko jedną rzecz na raz. Oznacza to, że jeśli procesor napotka funkcję, która zajmuje dużo czasu, JavaScript czeka, aż cała funkcja zostanie wykonana, zanim przejdzie do innych części programu.

Większość funkcji jest wykonywana w całości przez procesor. Oznacza to, że podczas wykonywania wspomnianych funkcji, niezależnie od tego, jak długo to potrwa, procesor będzie całkowicie zajęty. Są to tak zwane funkcje synchroniczne. Poniżej zdefiniowano przykładową funkcję synchroniczną:

function add(a, b) {
    for (let i = 0; i < 1000000; i ++) {
        // Do nothing
    }
    return a + b;
}

// Calling the function will take a long time
sum = add(10, 5);

// However, the processor cannot move to the 
console.log(sum);

Ta funkcja wykonuje dużą pętlę, której wykonanie zajmuje dużo czasu, zanim zwróci sumę swoich dwóch argumentów.

Po zdefiniowaniu funkcji wywołaliśmy ją i zapisaliśmy jej wynik w zmiennej sum. Następnie zarejestrowaliśmy wartość zmiennej sum. Mimo że wykonanie funkcji dodawania zajmuje trochę czasu, procesor nie może przejść do rejestrowania sumy, dopóki wykonanie nie zostanie zakończone.

Zdecydowana większość napotkanych funkcji będzie zachowywać się w przewidywalny sposób, jak ten powyżej. Jednak niektóre funkcje są asynchroniczne i nie zachowują się jak zwykłe funkcje.

Wprowadzenie do funkcji asynchronicznej

Funkcje asynchroniczne wykonują większość swojej pracy poza procesorem. Oznacza to, że nawet jeśli wykonanie funkcji może trochę potrwać, procesor będzie wolny i będzie mógł wykonać więcej pracy.

Oto przykład takiej funkcji:

fetch('https://jsonplaceholder.typicode.com/users/1');

Aby zwiększyć wydajność, JavaScript umożliwia procesorowi przejście do innych zadań, które wymagają procesora, nawet przed zakończeniem wykonywania funkcji asynchronicznej.

Ponieważ procesor ruszył przed zakończeniem wykonywania funkcji asynchronicznej, jej wynik nie będzie natychmiast dostępny. To będzie w toku. Gdyby procesor próbował wykonać inne części programu, które zależały od oczekującego wyniku, otrzymalibyśmy błędy.

Dlatego procesor powinien wykonywać tylko te części programu, które nie zależą od oczekującego wyniku. W tym celu współczesny JavaScript wykorzystuje obietnice.

Czym jest Obietnica w JavaScript?

W JavaScript obietnica jest tymczasową wartością zwracaną przez funkcję asynchroniczną. Obietnice są podstawą współczesnego programowania asynchronicznego w JavaScript.

Po utworzeniu obietnicy dzieje się jedna z dwóch rzeczy. Albo rozwiązuje problem, gdy wartość zwracana przez funkcję asynchroniczną zostanie pomyślnie wygenerowana, albo odrzuca w przypadku błędu. Są to zdarzenia w ramach cyklu życia obietnicy. Dlatego możemy dołączyć procedury obsługi zdarzeń do obietnicy, która zostanie wywołana, gdy zostanie rozwiązana lub odrzucona.

Cały kod, który wymaga wartości końcowej funkcji asynchronicznej, można dołączyć do procedury obsługi zdarzeń obietnicy na czas jej rozwiązania. Cały kod obsługujący błąd odrzuconej obietnicy zostanie również dołączony do odpowiedniej procedury obsługi zdarzeń.

Oto przykład, w którym odczytujemy dane z pliku w Nodejs.

const fs = require('fs/promises');

fileReadPromise = fs.readFile('./hello.txt', 'utf-8');

fileReadPromise.then((data) => console.log(data));

fileReadPromise.catch((error) => console.log(error));

W pierwszej linii importujemy moduł fs/promises.

W drugim wierszu wywołujemy funkcję readFile, podając nazwę i kodowanie pliku, którego zawartość chcemy odczytać. Ta funkcja jest asynchroniczna; dlatego zwraca obietnicę. Przechowujemy obietnicę w zmiennej fileReadPromise.

W trzecim wierszu dołączyliśmy detektor zdarzeń na wypadek spełnienia obietnicy. Zrobiliśmy to, wywołując metodę then na obiekcie obietnicy. Jako argument do naszego wywołania metody then przekazaliśmy funkcję do uruchomienia, jeśli i kiedy obietnica zostanie rozwiązana.

W czwartej linijce dołączyliśmy słuchacza na wypadek odrzucenia obietnicy. Odbywa się to poprzez wywołanie metody catch i przekazanie procedury obsługi zdarzenia błędu jako argumentu.

Alternatywnym podejściem jest użycie słów kluczowych async i await. Omówimy to podejście dalej.

Wyjaśnienie asynchronizacji i oczekiwania

Słowa kluczowe Async i Await mogą być używane do pisania asynchronicznego JavaScript w sposób, który wygląda lepiej składniowo. W tej sekcji wyjaśnię, jak używać słów kluczowych i jaki mają wpływ na Twój kod.

Słowo kluczowe await służy do wstrzymania wykonywania funkcji podczas oczekiwania na zakończenie funkcji asynchronicznej. Oto przykład:

const fs = require('fs/promises');

function readData() {
	const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available
	console.log(data);
}

readData()

Użyliśmy słowa kluczowego await podczas wywołania funkcji readFile. To poinstruowało procesor, aby poczekał, aż plik zostanie odczytany, zanim będzie można wykonać następny wiersz (log konsoli). Pomaga to zapewnić, że kod zależny od wyniku funkcji asynchronicznej nie zostanie wykonany, dopóki wynik nie będzie dostępny.

Jeśli spróbujesz uruchomić powyższy kod, napotkasz błąd. Wynika to z faktu, że await można używać tylko wewnątrz funkcji asynchronicznej. Aby zadeklarować funkcję jako asynchroniczną, należy użyć słowa kluczowego async przed deklaracją funkcji w następujący sposób:

const fs = require('fs/promises');

async function readData() {
	const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available
	console.log(data);
}

// Calling the function so it runs
readData()

// Code at this point will run while waiting for the readData function to complete
console.log('Waiting for the data to complete')

Uruchamiając ten fragment kodu, zobaczysz, że JavaScript wykonuje zewnętrzny plik console.log, czekając na udostępnienie danych odczytanych z pliku tekstowego. Gdy jest dostępny, wykonywany jest plik console.log wewnątrz readData.

Obsługa błędów podczas używania słów kluczowych async i await jest zwykle wykonywana przy użyciu próbuj złapać Bloki. Ważne jest również, aby wiedzieć, jak to zrobić pętla z kodem asynchronicznym.

Async i await są dostępne w nowoczesnym JavaScript. Tradycyjnie kod asynchroniczny był pisany przy użyciu wywołań zwrotnych.

Wprowadzenie do wywołań zwrotnych

Wywołanie zwrotne to funkcja, która zostanie wywołana, gdy wynik będzie dostępny. Cały kod, który wymaga wartości zwracanej, zostanie umieszczony wewnątrz wywołania zwrotnego. Wszystko inne poza wywołaniem zwrotnym nie zależy od wyniku i dlatego jest dowolne do wykonania.

Oto przykład, który odczytuje plik w Nodejs.

const fs = require("fs");

fs.readFile("./hello.txt", "utf-8", (err, data) => {

	// In this callback, we put all code that requires 
	if (err) console.log(err);
	else console.log(data);
});

// In this part here we can perform all the tasks that do not require the result
console.log("Hello from the program")

W pierwszym wierszu zaimportowaliśmy moduł fs. Następnie wywołaliśmy funkcję readFile modułu fs. Funkcja readFile odczyta tekst z określonego przez nas pliku. Pierwszy argument określa, który to plik, a drugi określa format pliku.

Funkcja readFile odczytuje tekst z plików asynchronicznie. Aby to zrobić, przyjmuje funkcję jako argument. Ten argument funkcji jest funkcją wywołania zwrotnego i zostanie wywołana po odczytaniu danych.

Pierwszym argumentem przekazywanym podczas wywoływania funkcji wywołania zwrotnego jest błąd, który będzie miał wartość, jeśli błąd wystąpi podczas działania funkcji. Jeśli nie zostanie napotkany żaden błąd, będzie on niezdefiniowany.

Drugim argumentem przekazywanym do wywołania zwrotnego są dane odczytane z pliku. Kod wewnątrz tej funkcji uzyska dostęp do danych z pliku. Kod poza tą funkcją nie wymaga danych z pliku; dlatego można je wykonać podczas oczekiwania na dane z pliku.

Uruchomienie powyższego kodu dałoby następujący wynik:

Kluczowe funkcje JavaScript

Istnieje kilka kluczowych funkcji i cech, które wpływają na działanie asynchronicznego JavaScript. Zostały one dobrze wyjaśnione w poniższym filmie:

Poniżej pokrótce omówiłem dwie ważne cechy.

# 1. Jednowątkowy

W przeciwieństwie do innych języków, które pozwalają programiście na używanie wielu wątków, JavaScript pozwala na użycie tylko jednego wątku. Wątek to sekwencja instrukcji, które logicznie zależą od siebie. Wiele wątków umożliwia programowi wykonanie innego wątku w przypadku napotkania operacji blokujących.

Jednak wiele wątków zwiększa złożoność i utrudnia zrozumienie programów, które ich używają. To zwiększa prawdopodobieństwo, że w kodzie zostaną wprowadzone błędy, a debugowanie kodu będzie trudne. Dla uproszczenia JavaScript został wykonany jako jednowątkowy. Jako język jednowątkowy opiera się na sterowaniu zdarzeniami w celu wydajnej obsługi operacji blokowania.

#2. Sterowany zdarzeniami

JavaScript jest również sterowany zdarzeniami. Oznacza to, że niektóre zdarzenia mają miejsce podczas cyklu życia programu JavaScript. Jako programista możesz dołączyć funkcje do tych zdarzeń, a za każdym razem, gdy zdarzenie nastąpi, dołączona funkcja zostanie wywołana i wykonana.

Niektóre zdarzenia mogą wynikać z dostępności wyniku operacji blokującej. W takim przypadku powiązana funkcja jest następnie wywoływana z wynikiem.

Rzeczy do rozważenia podczas pisania asynchronicznego JavaScriptu

W tej ostatniej sekcji wspomnę o kilku rzeczach, które należy wziąć pod uwagę podczas pisania asynchronicznego JavaScriptu. Obejmuje to obsługę przeglądarek, najlepsze praktyki i znaczenie.

Obsługa przeglądarki

To jest tabela przedstawiająca obsługę obietnic w różnych przeglądarkach.

Źródło: caniuse.com

To jest tabela przedstawiająca obsługę asynchronicznych słów kluczowych w różnych przeglądarkach.

Źródło: caniuse.com

Najlepsze praktyki

  • Zawsze wybieraj asynchronizację/oczekiwanie, ponieważ pomaga to pisać czystszy kod, który jest łatwy do przemyślenia.
  • Obsługa błędów w blokach try/catch.
  • Używaj słowa kluczowego async tylko wtedy, gdy konieczne jest oczekiwanie na wynik funkcji.

Znaczenie kodu asynchronicznego

Kod asynchroniczny umożliwia pisanie bardziej wydajnych programów, które używają tylko jednego wątku. Jest to ważne, ponieważ JavaScript jest używany do tworzenia stron internetowych, które wykonują wiele operacji asynchronicznych, takich jak żądania sieciowe oraz odczytywanie lub zapisywanie plików na dysku. Ta wydajność umożliwiła wzrost popularności środowisk wykonawczych, takich jak NodeJS, jako preferowanego środowiska wykonawczego dla serwerów aplikacji.

Ostatnie słowa

To był długi artykuł, ale udało nam się w nim omówić, czym różnią się funkcje asynchroniczne od zwykłych funkcji synchronicznych. Omówiliśmy również, jak używać kodu asynchronicznego przy użyciu samych obietnic, słów kluczowych async/await i wywołań zwrotnych.

Ponadto omówiliśmy kluczowe funkcje języka JavaScript. W ostatniej sekcji omówiliśmy obsługę przeglądarek i najlepsze praktyki.

Następnie zapoznaj się z najczęściej zadawanymi pytaniami podczas rozmowy kwalifikacyjnej Node.js.