Node.js
Programowanie asynchroniczne
Szukaj…
Wprowadzenie
Węzeł to język programowania, w którym wszystko może działać w sposób asynchroniczny. Poniżej znajduje się kilka przykładów i typowych rzeczy związanych z pracą asynchroniczną.
Składnia
- doSomething ([args], function ([argsCB]) {/ * zrób coś po zakończeniu * /});
- doSomething ([args], ([argsCB]) => {/ * zrób coś po zakończeniu * /});
Funkcje oddzwaniania
Funkcje zwrotne w JavaScript
Funkcje zwrotne są powszechne w JavaScript. Funkcje zwrotne są możliwe w JavaScript, ponieważ są pierwszorzędnymi obywatelami .
Synchroniczne połączenia zwrotne.
Funkcje oddzwaniania mogą być synchroniczne lub asynchroniczne. Ponieważ asynchroniczne funkcje zwrotne mogą być bardziej złożone, oto prosty przykład synchronicznej funkcji zwrotnej.
// a function that uses a callback named `cb` as a parameter
function getSyncMessage(cb) {
cb("Hello World!");
}
console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getSyncMessage(function(message) {
console.log(message);
});
console.log("After getSyncMessage call");
Dane wyjściowe powyższego kodu to:
> Before getSyncMessage call
> Hello World!
> After getSyncMessage call
Najpierw omówimy sposób wykonania powyższego kodu. Jest to bardziej dla tych, którzy jeszcze nie rozumieją pojęcia oddzwaniania, jeśli już rozumiesz, możesz pominąć ten akapit. Najpierw kod jest analizowany, a następnie pierwszą interesującą rzeczą jest wykonanie wiersza 6, który wyprowadza Before getSyncMessage call
do konsoli. Następnie wykonywana jest linia 8, która wywołuje funkcję getSyncMessage
wysyłającą anonimową funkcję jako argument parametru o nazwie cb
w funkcji getSyncMessage
. Wykonywanie jest teraz wykonywane wewnątrz funkcji getSyncMessage
w linii 3, która wykonuje właśnie przekazaną funkcję cb
, to wywołanie wysyła ciąg argumentu „Hello World” dla message
nazwie param w przekazanej funkcji anonimowej. Wykonanie następnie przechodzi do wiersza 9, który rejestruje Hello World!
do konsoli. Następnie wykonanie przechodzi przez proces opuszczania stosu wywołań ( patrz także ), uderzając w linię 10, następnie linię 4, a następnie z powrotem do linii 11.
Kilka informacji, które należy wiedzieć o połączeniach zwrotnych w ogóle:
- Funkcja wysyłana do funkcji jako wywołanie zwrotne może być wywoływana zero razy, raz lub wiele razy. Wszystko zależy od implementacji.
- Funkcja zwrotna może być wywoływana synchronicznie lub asynchronicznie i być może zarówno synchronicznie, jak i asynchronicznie.
- Podobnie jak w przypadku normalnych funkcji, nazwy, które nadajesz parametrom swojej funkcji, nie są ważne, ale kolejność jest taka. Na przykład w wierszu 8
message
parametru mógł mieć nazwęstatement
,msg
lub jeśli jesteś nonsensowny, jakjellybean
. Powinieneś więc wiedzieć, jakie parametry są wysyłane do Twojego wywołania zwrotnego, abyś mógł ustawić je we właściwej kolejności z odpowiednimi nazwami.
Asynchroniczne wywołania zwrotne.
Jedną z rzeczy, na które należy zwrócić uwagę w JavaScript, jest domyślnie synchroniczny, ale w środowisku są podane interfejsy API (przeglądarka, Node.js itp.), Które mogą sprawić, że będzie on asynchroniczny (więcej na ten temat tutaj ).
Niektóre typowe rzeczy, które są asynchroniczne w środowiskach JavaScript akceptujących wywołania zwrotne:
- Wydarzenia
- setTimeout
- setInterval
- interfejs API pobierania
- Obietnice
Również każda funkcja, która korzysta z jednej z powyższych funkcji, może być opakowana funkcją, która odbiera wywołanie zwrotne, a wówczas wywołanie zwrotne byłoby wówczas wywołaniem zwrotnym asynchronicznym (chociaż zawijanie obietnic funkcją odbierającą wywołanie zwrotne prawdopodobnie byłoby uważane za anty-wzorzec jako są bardziej preferowane sposoby obsługi obietnic).
Biorąc pod uwagę te informacje, możemy zbudować funkcję asynchroniczną podobną do powyższej funkcji synchronicznej.
// a function that uses a callback named `cb` as a parameter
function getAsyncMessage(cb) {
setTimeout(function () { cb("Hello World!") }, 1000);
}
console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getAsyncMessage(function(message) {
console.log(message);
});
console.log("After getSyncMessage call");
Który drukuje następujące informacje na konsoli:
> Before getSyncMessage call
> After getSyncMessage call
// pauses for 1000 ms with no output
> Hello World!
Wykonanie linii przechodzi do dzienników linii 6 „Przed wywołaniem getSyncMessage”. Następnie wykonanie przechodzi do wiersza 8 wywołującego getAsyncMessage z wywołaniem zwrotnym dla parametru param cb
. Następnie wykonywana jest linia 3, która wywołuje setTimeout z wywołaniem zwrotnym jako pierwszym argumentem, a liczbą 300 jako drugim argumentem. setTimeout
robi wszystko, co robi i zachowuje to wywołanie zwrotne, aby mógł je później wywołać za 1000 milisekund, ale po skonfigurowaniu limitu czasu i przed wstrzymaniem 1000 milisekund przekazuje wykonanie do miejsca, w którym zostało przerwane, więc przechodzi do wiersza 4 , następnie wiersz 11, a następnie zatrzymuje się na 1 sekundę, a następnie setTimeout wywołuje funkcję wywołania zwrotnego, która przenosi wykonanie z powrotem do wiersza 3, w którym wywołanie zwrotne getAsyncMessages
jest wywoływane z wartością „Hello World” dla message
parametru, który jest następnie logowany do konsoli w wierszu 9 .
Funkcje zwrotne w Node.js
NodeJS ma asynchroniczne wywołania zwrotne i zwykle dostarcza dwa parametry do twoich funkcji, czasami konwencjonalnie nazywane err
i data
. Przykład z czytaniem tekstu pliku.
const fs = require("fs");
fs.readFile("./test.txt", "utf8", function(err, data) {
if(err) {
// handle the error
} else {
// process the file text given with data
}
});
To jest przykład wywołania zwrotnego, które jest wywoływane jednorazowo.
Dobrą praktyką jest jakoś radzić sobie z błędem, nawet jeśli tylko go rejestrujesz lub rzucasz. Pozostałe nie jest konieczne, jeśli rzucasz lub wracasz i można je usunąć, aby zmniejszyć wcięcie, o ile zatrzymasz wykonywanie bieżącej funkcji w if, wykonując coś takiego jak rzucanie lub powrót.
Choć może to być wspólne, aby zobaczyć err
, data
nie zawsze może być przypadek, że wywołania zwrotne użyje tego wzoru jest to najlepiej spojrzeć na dokumentacji.
Kolejny przykład wywołania zwrotnego pochodzi z biblioteki ekspresowej (express 4.x):
// this code snippet was on http://expressjs.com/en/4x/api.html
const express = require('express');
const app = express();
// this app.get method takes a url route to watch for and a callback
// to call whenever that route is requested by a user.
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
Ten przykład pokazuje wywołanie zwrotne, które jest wywoływane wiele razy. Oddzwanianie jest dostarczane z dwoma obiektami jako parametrami nazwanymi tutaj jako req
i res
Nazwy te odpowiadają odpowiednio żądaniu i odpowiedzi, i zapewniają sposoby przeglądania przychodzącego żądania i ustawienia odpowiedzi, która zostanie wysłana do użytkownika.
Jak widać, istnieje wiele sposobów na wywołanie zwrotne w celu wykonania synchronizacji i asynchronizacji kodu w JavaScript, a wywołania zwrotne są bardzo wszechobecne w całym JavaScript.
Przykład kodu
Pytanie: Jaki jest wynik kodu poniżej i dlaczego?
setTimeout(function() {
console.log("A");
}, 1000);
setTimeout(function() {
console.log("B");
}, 0);
getDataFromDatabase(function(err, data) {
console.log("C");
setTimeout(function() {
console.log("D");
}, 1000);
});
console.log("E");
Wyjście: Na pewno wiadomo: EBAD
. C
nie jest znane, kiedy zostanie zalogowany.
Objaśnienie: Kompilator nie zatrzyma się na getDataFromDatabase
setTimeout
i getDataFromDatabase
. Więc pierwszą linią, którą będzie logował, jest E
Funkcje wywołania zwrotnego (pierwszy argument setTimeout
) będą działać po ustawionym czasie w asynchroniczny sposób!
Więcej szczegółów:
-
E
nie masetTimeout
-
B
ma ustawiony limit czasu równy 0 milisekundom - Ma ustawić czas 1000 milisekund
-
D
musi zażądać bazy danych, po to musiD
czekać 1000 milisekund tak chodzi poA
. -
C
jest nieznany, ponieważ nie wiadomo, kiedy wymagane są dane z bazy danych. Może to być przed lub poA
Obsługa błędów asynchronicznych
Próbuj złapać
Błędy muszą być zawsze obsługiwane. Jeśli używasz programowania synchronicznego, możesz użyć try catch
. Ale to nie działa, jeśli pracujesz asynchronicznie! Przykład:
try {
setTimeout(function() {
throw new Error("I'm an uncaught error and will stop the server!");
}, 100);
}
catch (ex) {
console.error("This error will not be work in an asynchronous situation: " + ex);
}
Błędy asynchroniczne będą obsługiwane tylko w funkcji wywołania zwrotnego!
Możliwości pracy
Obsługa zdarzeń
Pierwsze wersje Node.JS mają moduł obsługi zdarzeń.
process.on("UncaughtException", function(err, data) {
if (err) {
// error handling
}
});
Domeny
W domenie błędy są usuwane za pośrednictwem emiterów zdarzeń. Korzystając z tego, wszystkie błędy, liczniki czasu i metody wywołania zwrotnego są domyślnie rejestrowane tylko w domenie. Przez błąd, bądź zdarzeniem błędu wysłania i nie spowodowało awarii aplikacji.
var domain = require("domain");
var d1 = domain.create();
var d2 = domain.create();
d1.run(function() {
d2.add(setTimeout(function() {
throw new Error("error on the timer of domain 2");
}, 0));
});
d1.on("error", function(err) {
console.log("error at domain 1: " + err);
});
d2.on("error", function(err) {
console.log("error at domain 2: " + err);
});
Piekło zwrotne
Piekło wywołania zwrotnego (również piramida zagłady lub efekt bumerangu) powstaje, gdy zagnieździsz zbyt wiele funkcji wywołania zwrotnego w funkcji wywołania zwrotnego. Oto przykład odczytu pliku (w ES6).
const fs = require('fs');
let filename = `${__dirname}/myfile.txt`;
fs.exists(filename, exists => {
if (exists) {
fs.stat(filename, (err, stats) => {
if (err) {
throw err;
}
if (stats.isFile()) {
fs.readFile(filename, null, (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
}
else {
throw new Error("This location contains not a file");
}
});
}
else {
throw new Error("404: file not found");
}
});
Jak uniknąć „Callback Hell”
Zaleca się zagnieżdżanie nie więcej niż 2 funkcji zwrotnych. Pomoże to utrzymać czytelność kodu i znacznie łatwiej będzie utrzymać go w przyszłości. Jeśli musisz zagnieździć więcej niż 2 oddzwaniania, spróbuj zamiast tego użyć zdarzeń rozproszonych .
Istnieje również biblioteka o nazwie async, która pomaga zarządzać wywołaniami zwrotnymi i ich wykonywaniem dostępnym na npm. Zwiększa czytelność kodu wywołania zwrotnego i daje większą kontrolę nad przepływem kodu wywołania zwrotnego, w tym umożliwia uruchamianie ich równolegle lub szeregowo.
Rodzime obietnice
Obietnice są narzędziem do programowania asynchronicznego. W JavaScript obietnice znane są z ich then
metod. Obietnice mają dwa główne stany: „w toku” i „rozstrzygnięte”. Gdy „obietnica” zostanie „rozliczona”, nie może wrócić do „w toku”. Oznacza to, że obietnice są w większości dobre dla wydarzeń, które występują tylko raz. Stan „ustalony” ma również dwa państwa „rozwiązany” i „odrzucony”. Możesz utworzyć nową obietnicę za pomocą new
słowa kluczowego i przekazać funkcję do konstruktora new Promise(function (resolve, reject) {})
.
Funkcja przekazana do konstruktora Promise zawsze odbiera pierwszy i drugi parametr, zwykle nazywany odpowiednio „ resolve
i „ reject
. Nazewnictwo tych dwóch parametrów jest konwencją, ale wprowadzą obietnicę w stan „rozwiązany” lub „odrzucony”. Kiedy którykolwiek z nich jest nazywany, obietnica przechodzi od „oczekiwania” do „rozliczenia”. resolve
jest wywoływane, gdy pożądana czynność, która często jest asynchroniczna, została wykonana, a reject
jest stosowane, jeśli akcja została błędna.
W poniższym limicie czasu jest funkcja, która zwraca obietnicę.
function timeout (ms) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("It was resolved!");
}, ms)
});
}
timeout(1000).then(function (dataFromPromise) {
// logs "It was resolved!"
console.log(dataFromPromise);
})
console.log("waiting...");
wyjście konsoli
waiting...
// << pauses for one second>>
It was resolved!
Po wywołaniu limitu czasu funkcja przekazana do konstruktora Promise jest wykonywana niezwłocznie. Następnie wykonywana jest metoda setTimeout, a jej wywołanie zwrotne jest uruchamiane w ciągu kolejnych ms
milisekund, w tym przypadku ms=1000
. Ponieważ wywołanie zwrotne do setTimeout nie zostało uruchomione, funkcja timeout zwraca kontrolę nad zasięgiem wywoływania. Łańcuch then
metod są następnie zapisywane na miano później kiedy / jeśli obietnica rozwiązany. Gdyby istniały tutaj metody catch
, byłyby również przechowywane, ale zostałyby zwolnione, gdy / jeśli obietnica „odrzuci”.
Skrypt następnie wypisuje „czeka ...”. Sekundę później setTimeout wywołuje funkcję zwrotną, która wywołuje funkcję resolving z ciągiem „It was resolved!”. Ten ciąg jest następnie przekazywany do wywołania zwrotnego metody then
, a następnie logowany do użytkownika.
W tym samym sensie możesz zawinąć asynchroniczną funkcję setTimeout, która wymaga wywołania zwrotnego, możesz zawinąć każdą pojedynczą akcję asynchroniczną z obietnicą.
Przeczytaj więcej o obietnicach w dokumentacji JavaScript Obietnice .