Jak działa środowisko Node.js

Ukończone

W tej lekcji wyjaśniono, jak platforma Node.js obsługuje zadania przychodzące do środowiska uruchomieniowego języka JavaScript.

Typy zadań

Aplikacje JavaScript mają dwa typy zadań:

  • Zadania synchroniczne: te zadania są wykonywane w kolejności. Nie są one zależne od innego zasobu do ukończenia. Przykłady to operacje matematyczne lub manipulowanie ciągami.
  • Asynchroniczne: te zadania mogą nie zostać wykonane natychmiast, ponieważ są zależne od innych zasobów. Przykłady to żądania sieciowe lub operacje systemu plików.

Ponieważ chcesz, aby program działał tak szybko, jak to możliwe, chcesz, aby aparat JavaScript mógł kontynuować pracę, czekając na odpowiedź z operacji asynchronicznej. Aby to zrobić, dodaje zadanie asynchroniczne do kolejki zadań i kontynuuje pracę nad następnym zadaniem.

Zarządzanie kolejką zadań za pomocą pętli zdarzeń

Biblioteka Node.js używa architektury sterowanej zdarzeniami aparatu JavaScript do przetwarzania żądań asynchronicznych. Na poniższym diagramie przedstawiono działanie pętli zdarzeń V8 na wysokim poziomie:

Diagram showing how Node J S uses an event-driven architecture where an event loop processes events and returns callbacks.

Zadanie asynchroniczne oznaczone odpowiednią składnią (pokazaną poniżej) jest dodawane do pętli zdarzeń. Zadanie obejmuje pracę, która ma zostać wykonana, oraz funkcję wywołania zwrotnego w celu odbierania wyników. Po zakończeniu operacji intensywnej wywołanie zwrotne zostanie wyzwolone z wynikami.

Operacje synchroniczne a operacje asynchroniczne

Interfejsy API Node.js zapewniają zarówno operacje asynchroniczne, jak i synchroniczne dla niektórych z tych samych operacji, takich jak operacje na plikach. Ogólnie rzecz biorąc, zawsze należy myśleć asynchronicznie, ale czasami mogą być używane operacje synchroniczne.

Przykładem jest, gdy interfejs wiersza polecenia odczytuje plik, a następnie natychmiast używa danych w pliku. W takim przypadku można użyć synchronicznej wersji operacji pliku, ponieważ nie ma innego systemu ani osoby oczekującej na użycie aplikacji.

Jeśli jednak tworzysz serwer internetowy, zawsze należy używać asynchronicznej wersji operacji pliku, aby nie blokować możliwości wykonywania pojedynczego wątku w celu przetwarzania innych żądań użytkowników.

W swojej pracy jako deweloper w firmie TailWind Traders musisz zrozumieć różnicę między operacjami synchronicznymi i asynchronicznymi oraz kiedy należy ich używać.

Wydajność za pomocą operacji asynchronicznych

Biblioteka Node.js korzysta z unikatowego charakteru opartego na zdarzeniach języka JavaScript, który sprawia, że tworzenie zadań serwera jest szybkie i wydajne. Język JavaScript, jeśli jest używany poprawnie z technikami asynchronicznymi, może generować takie same wyniki wydajności jak języki niskiego poziomu, takie jak C , ze względu na zwiększenie wydajności, które umożliwił aparat V8.

Techniki asynchroniczne są dostępne w 3 stylach, które należy rozpoznać w pracy:

  • Asynchroniczna/await (zalecana): najnowsza technika asynchroniczna, która używa async słów kluczowych i await do odbierania wyników operacji asynchronicznej. Funkcja Async/await jest używana w wielu językach programowania. Ogólnie rzecz biorąc, nowe projekty z nowszymi zależnościami będą używać tego stylu kodu asynchronicznego.
  • Wywołania zwrotne: oryginalna technika asynchroniczna, która używa funkcji wywołania zwrotnego do odbierania wyników operacji asynchronicznej. Zobaczysz to w starszych bazach kodu i w starszych interfejsach API Node.js.
  • Obietnice: Nowsza technika asynchroniczna, która używa obiektu obietnicy do odbierania wyników operacji asynchronicznej. Zobaczysz to w nowszych bazach kodu i w nowszych interfejsach API Node.js. Może być konieczne napisanie kodu opartego na obietnicach w pracy w celu opakowania starszych interfejsów API, które nie zostaną zaktualizowane. Korzystając z obietnic dla tego zawijania, można zezwolić na użycie kodu w większym zakresie projektów w wersji Node.js niż w nowszym stylu asynchronicznego/await kodu.

Async/await

Async/await to najnowszy sposób obsługi programowania asynchronicznego. Async/await to cukier syntatyczny na szczycie obietnic i sprawia, że kod asynchroniczny wygląda bardziej jak kod synchroniczny. Łatwiej jest również odczytywać i konserwować.

Ten sam przykład użycia async/await wygląda następująco:

// async/await asynchronous example

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

const filePath = './file.txt';

// `async` before the parent function
async function readFileAsync() {
  try {
    // `await` before the async method
    const data = await fs.readFile(filePath, 'utf-8');
    console.log(data);
    console.log('Done!');
  } catch (error) {
    console.log('An error occurred...: ', error);
  }
}

readFileAsync()
  .then(() => {
    console.log('Success!');
  })
  .catch((error) => {
    console.log('An error occurred...: ', error);
  });

Gdy async/await został wydany w ES2017, słowa kluczowe mogą być używane tylko w funkcjach z funkcją najwyższego poziomu jest obietnicą. Chociaż obietnica nie musiała mieć then i catch sekcje, nadal trzeba było mieć promise składnię do uruchomienia.

async Funkcja zawsze zwraca obietnicę, nawet jeśli nie ma w niej wywołaniaawait. Obietnica zostanie rozwiązana z wartością zwróconą przez funkcję. Jeśli funkcja zgłosi błąd, obietnica zostanie odrzucona z zwróconą wartością.

Obietnice

Ponieważ zagnieżdżone wywołania zwrotne mogą być trudne do odczytania i zarządzania, środowisko Node.js dodało obsługę obietnic. Obietnica to obiekt reprezentujący ukończenie ostateczne (lub niepowodzenie) operacji asynchronicznej.

Funkcja promise ma format:

// Create a basic promise function
function promiseFunction() {
  return new Promise((resolve, reject) => {
    // do something

    if (error) {
      // indicate success
      reject(error);
    } else {
      // indicate error
      resolve(data);
    }
  });
}

// Call a basic promise function
promiseFunction()
  .then((data) => {
    // handle success
  })
  .catch((error) => {
    // handle error
  });

Metoda then jest wywoływana po spełnieniu obietnicy, a catch metoda jest wywoływana po odrzuceniu obietnicy.

Aby odczytać plik asynchronicznie z obietnicami, kod to:

// promises asynchronous example

const fs = require('fs').promises;
const filePath = './file.txt';

// request to read a file
fs.readFile(filePath, 'utf-8')
  .then((data) => {
    console.log(data);
    console.log('Done!');
  })
  .catch((error) => {
    console.log('An error occurred...: ', error);
  });

console.log(`I'm the last line of the file!`);

Async/await najwyższego poziomu

Najnowsze wersje środowiska Node.js dodały async/await najwyższego poziomu dla modułów ES6. Musisz dodać właściwość o nazwie type w pliku package.json z wartością , module aby użyć tej funkcji.

{
    "type": "module"
}

Następnie możesz użyć await słowa kluczowego na najwyższym poziomie kodu

// top-level async/await asynchronous example

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

const filePath = './file.txt';

// `async` before the parent function
try {
  // `await` before the async method
  const data = await fs.readFile(filePath, 'utf-8');
  console.log(data);
  console.log('Done!');
} catch (error) {
  console.log('An error occurred...: ', error);
}
console.log("I'm the last line of the file!");

Wywołania zwrotne

Po wydaniu biblioteki Node.js programowanie asynchroniczne było obsługiwane przy użyciu funkcji wywołania zwrotnego. Wywołania zwrotne to funkcje, które są przekazywane jako argumenty do innych funkcji. Po zakończeniu zadania wywoływana jest funkcja wywołania zwrotnego.

Kolejność parametrów funkcji jest ważna. Funkcja wywołania zwrotnego jest ostatnim parametrem funkcji.

// Callback function is the last parameter
function(param1, param2, paramN, callback)

Nazwa funkcji w kodzie, który utrzymujesz, może nie być wywoływana .callback Może być wywoływany cb lub done .next Nazwa funkcji nie jest ważna, ale kolejność parametrów jest ważna.

Zwróć uwagę, że nie ma żadnych wskazówek syntatycznych, że funkcja jest asynchroniczna. Musisz wiedzieć, że funkcja jest asynchroniczna, czytając dokumentację lub kontynuując czytanie kodu.

Przykład wywołania zwrotnego z nazwaną funkcją wywołania zwrotnego

Poniższy kod oddziela funkcję async od wywołania zwrotnego. Jest to łatwe do odczytania i zrozumienia oraz umożliwia ponowne użycie wywołania zwrotnego dla innych funkcji asynchronicznych.

// callback asynchronous example

// file system module from Node.js
const fs = require('fs');

// relative path to file
const filePath = './file.txt';

// callback
const callback = (error, data) => {
  if (error) {
    console.log('An error occurred...: ', error);
  } else {
    console.log(data); // Hi, developers!
    console.log('Done!');
  }
};

// async request to read a file
//
// parameter 1: filePath
// parameter 2: encoding of utf-8
// parmeter 3: callback function
fs.readFile(filePath, 'utf-8', callback);

console.log("I'm the last line of the file!");

Prawidłowy wynik to:

I'm the last line of the file!
Hi, developers!
Done!

Najpierw funkcja asynchroniczna jest uruchamiana fs.readFile i przechodzi do pętli zdarzeń. Następnie wykonanie kodu jest kontynuowane do następnego wiersza kodu, czyli ostatniego console.log. Po odczytaniu pliku wywoływana jest funkcja wywołania zwrotnego, a dwie instrukcje console.log są wykonywane.

Przykład wywołania zwrotnego z funkcją anonimową

W poniższym przykładzie użyto funkcji anonimowego wywołania zwrotnego, co oznacza, że funkcja nie ma nazwy i nie może być ponownie używana przez inne funkcje anonimowe.

// callback asynchronous example

// file system module from Node.js
const fs = require('fs');

// relative path to file
const filePath = './file.txt';

// async request to read a file
//
// parameter 1: filePath
// parameter 2: encoding of utf-8
// parmeter 3: callback function () => {}
fs.readFile(filePath, 'utf-8', (error, data) => {
  if (error) {
    console.log('An error occurred...: ', error);
  } else {
    console.log(data); // Hi, developers!
    console.log('Done!');
  }
});

console.log("I'm the last line of the file!");

Prawidłowy wynik to:

I'm the last line of the file!
Hi, developers!
Done!

Po wykonaniu kodu funkcja fs.readFile asynchroniczna jest uruchamiana i przechodzi do pętli zdarzeń. Następnie wykonanie będzie kontynuowane do następującego wiersza kodu, czyli ostatniego console.log. Gdy plik jest odczytywany, wywoływana jest funkcja wywołania zwrotnego, a dwie instrukcje console.log są wykonywane.

Zagnieżdżone wywołania zwrotne

Ze względu na to, że może być konieczne wywołanie kolejne wywołania zwrotnego asynchronicznego, a następnie inny kod wywołania zwrotnego może zostać zagnieżdżony. Jest to nazywane piekłem wywołania zwrotnego i jest trudne do odczytania i utrzymania.

// nested callback example

// file system module from Node.js
const fs = require('fs');

fs.readFile(param1, param2, (error, data) => {
  if (!error) {
    fs.writeFile(paramsWrite, (error, data) => {
      if (!error) {
        fs.readFile(paramsRead, (error, data) => {
          if (!error) {
            // do something
          }
        });
      }
    });
  }
});

Synchroniczne interfejsy API

Plik Node.js ma również zestaw synchronicznych interfejsów API. Te interfejsy API blokują wykonywanie programu do momentu ukończenia zadania. Synchroniczne interfejsy API są przydatne, gdy chcesz odczytać plik, a następnie natychmiast użyć danych w pliku.

Funkcje synchroniczne (blokujące) w pliku Node.js używają konwencji nazewnictwa elementu functionSync. Na przykład interfejs API asynchroniczny readFile ma synchroniczny odpowiednik o nazwie readFileSync. Ważne jest, aby zachować ten standard we własnych projektach, aby kod był łatwy do odczytania i zrozumienia.

// synchronous example

const fs = require('fs');

const filePath = './file.txt';

try {
  // request to read a file
  const data = fs.readFileSync(filePath, 'utf-8');
  console.log(data);
  console.log('Done!');
} catch (error) {
  console.log('An error occurred...: ', error);
}

Jako nowy deweloper w firmie TailWind Traders możesz poprosić o zmodyfikowanie dowolnego typu kodu Node.js. Ważne jest, aby zrozumieć różnicę między synchronicznymi i asynchronicznymi interfejsami API oraz różnymi składniami kodu asynchronicznego.