Node.js
Асинхронное программирование
Поиск…
Вступление
Узел - это язык программирования, где все может выполняться асинхронно. Ниже вы можете найти некоторые примеры и типичные вещи асинхронной работы.
Синтаксис
- doSomething ([args], function ([argsCB]) {/ * делать что-то, когда сделано * /});
- doSomething ([args], ([argsCB]) => {/ * делать что-то, когда сделано * /});
Функции обратного вызова
Функции обратного вызова в JavaScript
Функции обратного вызова распространены в JavaScript. Функции обратного вызова возможны в JavaScript, поскольку функции являются первоклассными гражданами .
Синхронные обратные вызовы.
Функции обратного вызова могут быть синхронными или асинхронными. Поскольку функции асинхронного обратного вызова могут быть более сложными, это простой пример синхронной функции обратного вызова.
// 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");
Вывод для вышеуказанного кода:
> Before getSyncMessage call
> Hello World!
> After getSyncMessage call
Сначала мы рассмотрим, как выполняется вышеуказанный код. Это больше для тех, кто еще не понимает концепцию обратных вызовов, если вы уже понимаете, что можете пропустить этот абзац. Сначала код анализируется, и тогда первая интересная вещь, которая должна произойти, выполняется строка 6, которая выводит Before getSyncMessage call
на консоль. Затем выполняется строка 8, которая вызывает функцию getSyncMessage
отправляющую анонимную функцию в качестве аргумента для параметра cb
в функции getSyncMessage
. Выполнение теперь выполняется внутри функции getSyncMessage
в строке 3, которая выполняет только что переданную функцию cb
, этот вызов отправляет строку аргумента «Hello World» для параметра с именем message
в переданной анонимной функции. Выполнение затем переходит к строке 9, в которой записывается Hello World!
на консоль. Затем выполнение проходит через процесс выхода из callstack ( см. Также ), попадая в линию 10, затем линию 4, а затем обратно в строку 11.
Некоторая информация, которая должна знать о обратных вызовах в целом:
- Функция, которую вы отправляете функции в качестве обратного вызова, может быть вызвана нулевым временем, раз или несколько раз. Все зависит от реализации.
- Функция обратного вызова может быть вызвана синхронно или асинхронно и, возможно, синхронно и асинхронно.
- Подобно нормальным функциям, имена, которые вы даете параметрам для вашей функции, не важны, но порядок. Так, например, в строке 8
message
параметра могло быть названоstatement
,msg
, или если вы нонсенсируете что-то вродеjellybean
. Поэтому вы должны знать, какие параметры отправлены в ваш обратный вызов, чтобы вы могли получить их в правильном порядке с именами.
Асинхронные обратные вызовы.
Одно примечание о JavaScript - это синхронно по умолчанию, но в среде есть API-интерфейсы (браузер, Node.js и т. Д.), Которые могли бы сделать его асинхронным (об этом здесь больше ).
Некоторые распространенные вещи, которые являются асинхронными в средах JavaScript, которые принимают обратные вызовы:
- События
- SetTimeout
- setInterval
- API выборки
- обещания
Также любая функция, которая использует одну из вышеперечисленных функций, может быть обернута функцией, которая выполняет обратный вызов, а обратный вызов будет асинхронным обратным вызовом (хотя обертывание обещаний с помощью функции, которая принимает обратный вызов, скорее всего, будет рассматриваться как анти-шаблон есть более предпочтительные способы обработки обещаний).
Поэтому, учитывая эту информацию, мы можем построить асинхронную функцию, аналогичную описанной выше синхронной.
// 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");
Что печатает на консоли следующее:
> Before getSyncMessage call
> After getSyncMessage call
// pauses for 1000 ms with no output
> Hello World!
Выполнение строки переходит к строкам 6 журналов «Перед вызовом getSyncMessage». Затем выполнение переходит к строке 8, вызывающей getAsyncMessage с обратным вызовом для параметра cb
. Затем выполняется строка 3, которая вызывает setTimeout с обратным вызовом в качестве первого аргумента, а число 300 - вторым аргументом. setTimeout
делает все, что он делает, и держится за этот обратный вызов, чтобы он мог вызывать его позже в 1000 миллисекунд, но после настройки таймаута и до того, как он приостанавливает 1000 миллисекунд, он передает выполнение обратно туда, где он остановился, поэтому он переходит в строку 4 , затем строка 11, а затем пауза в течение 1 секунды и setTimeout затем вызывает функцию обратного вызова, которая возвращает выполнение в строку 3, где getAsyncMessages
вызов getAsyncMessages
вызывается со значением «Hello World» для его message
параметра, которое затем регистрируется в консоли в строке 9 ,
Функции обратного вызова в Node.js
NodeJS имеет асинхронные обратные вызовы и обычно предоставляет два параметра для ваших функций, иногда условно называемых err
и data
. Пример с чтением файла.
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
}
});
Это пример обратного вызова, который называется одним временем.
Это хорошая практика, чтобы обрабатывать ошибку так или иначе, даже если вы просто регистрируете ее или бросаете ее. Другое необязательно, если вы бросаете или возвращаете и можете удалить, чтобы уменьшить отступ, пока вы прекратите выполнение текущей функции в if, выполнив что-то вроде метания или возврата.
Хотя может быть общим, чтобы видеть err
, data
могут быть не всегда так, что ваши обратные вызовы будут использовать этот шаблон, лучше всего посмотреть документацию.
Другой пример обратного вызова происходит из экспресс-библиотеки (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);
В этом примере показан обратный вызов, который вызывается несколько раз. Обратный вызов предоставляется с двумя объектами в качестве параметров, названных здесь как req
и res
эти имена соответствуют запросу и ответу соответственно, и они предоставляют способы просмотра входящего запроса и настройки ответа, который будет отправлен пользователю.
Как вы можете видеть, существуют различные способы обратного вызова, которые можно использовать для выполнения синхронизации и асинхронного кода в JavaScript, и обратные вызовы очень повсеместны для всего JavaScript.
Пример кода
Вопрос: Каков результат кода ниже и почему?
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");
Результат: это точно известно: EBAD
. C
неизвестен, когда он будет зарегистрирован.
Объяснение: Компилятор не остановится на getDataFromDatabase
setTimeout
и getDataFromDatabase
. Таким образом, первая строка, которую он будет записывать, - E
Функции обратного вызова (первый аргумент setTimeout
) будут выполняться после установленного таймаута асинхронным способом!
Подробнее:
-
E
не имеетsetTimeout
-
B
имеет заданный тайм-аут 0 миллисекунд -
A
имеет заданное время ожидания 1000 миллисекунд -
D
должен запросить базу данных после того, как она должнаD
ждать 1000 миллисекунд, чтобы она появилась послеA
-
C
неизвестен, поскольку он неизвестен, когда запрашиваются данные базы данных. Это может быть до или послеA
Обработка ошибок Async
Попробуй поймать
Ошибки всегда должны выполняться. Если вы используете синхронное программирование, вы можете использовать try catch
. Но это не работает, если вы работаете асинхронно! Пример:
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);
}
Ошибки Async будут обрабатываться только внутри функции обратного вызова!
Рабочие возможности
Обработчики событий
Первые версии Node.JS получили обработчик событий.
process.on("UncaughtException", function(err, data) {
if (err) {
// error handling
}
});
Домены
Внутри домена ошибки выпускаются через излучатели событий. Используя это все ошибки, таймеры, методы обратного вызова неявно регистрируются только внутри домена. По ошибке, отправляйте сообщение об ошибке и не сбой приложения.
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);
});
Обратный звонок
Адвокат обратного вызова (также пирамида гибели или эффекта бумеранга) возникает, когда вы устанавливаете слишком много функций обратного вызова внутри функции обратного вызова. Ниже приведен пример чтения файла (в 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");
}
});
Как избежать «Callback Hell»
Рекомендуется устанавливать не более двух функций обратного вызова. Это поможет вам сохранить читаемость кода и будет намного легче поддерживать в будущем. Если вам нужно вложить более двух обратных вызовов, попробуйте использовать распределенные события .
Также существует библиотека с именем async, которая помогает управлять обратными вызовами и их исполнением на npm. Это повышает читаемость кода обратного вызова и дает вам больше контроля над потоком кода обратного вызова, включая возможность запуска их параллельно или последовательно.
Родные обещания
Обещания - это инструмент для асинхронного программирования. В JavaScript обещание известно их then
методы. Обещания состоят из двух основных состояний: «ожидающих» и «обоснованных». Как только обещание «улажено», оно не может вернуться к «ожиданию». Это означает, что обещания в основном хороши для событий, которые происходят только один раз. «Устоявшееся» государство имеет два состояния, «разрешенные» и «отклоненные». Вы можете создать новое обещание с использованием new
ключевого слова и передать функцию в конструктор new Promise(function (resolve, reject) {})
.
Функция, переданная в конструктор Promise, всегда получает первый и второй параметры, обычно называемые соответственно resolve
и reject
. Именование этих двух параметров является условным, но они обещают либо «разрешенное» состояние, либо «отклоненное» состояние. Когда либо один из них называется обещанием, он переходит от «ожидающего» к «поселенному». resolve
вызывается, когда требуемое действие, которое часто является асинхронным, выполняется, и reject
используется, если действие было выполнено с ошибкой.
В приведенном ниже таймауте есть функция, которая возвращает Promise.
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...");
консольный выход
waiting...
// << pauses for one second>>
It was resolved!
Когда вызывается время ожидания, функция, переданная конструктору Promise, выполняется без задержки. Затем выполняется метод setTimeout, и его обратный вызов активируется в следующих миллисекундах ms
, в этом случае ms=1000
. Поскольку обратный вызов setTimeout еще не запущен, функция тайм-аута возвращает управление вызывающей области. Цепочка , then
методы , которые затем сохраняются будет называться позже , когда / если обещание решено. Если бы здесь были методы catch
они также были бы сохранены, но были бы уволены, когда / если обещание «отвергает».
Затем скрипт печатает «ожидание ...». Через секунду setTimeout вызывает свой обратный вызов, который вызывает функцию разрешения со строкой «Это было разрешено!». Затем эта строка передается в обратный вызов метода then
и затем регистрируется пользователем.
В этом же смысле вы можете обернуть асинхронную функцию setTimeout, которая требует обратного вызова, вы можете обернуть любое сингулярное асинхронное действие с обещанием.
Подробнее о обещаниях в документации JavaScript Promises .