$ curl http://localhost:8000/file.txt
hello
$ curl -X DELETE http://localhost:8000/file.txt
$ curl http://localhost:8000/file.txt
File not found
Первый запрос к file.txt
завершается с ошибкой, поскольку файла ещё нет. Запрос PUT
создаёт файл, и глядите-ка – следующий запрос его успешно получает. После его удаления через DELETE
файл снова отсутствует.
Обработка ошибок
В коде файлового сервера есть шесть мест, где мы перенаправляем исключения, когда мы не знаем, как обрабатывать ошибки. Поскольку исключения не передаются автоматически в функции обратного вызова, но передаются им как аргументы, их надо каждый раз обрабатывать персонально. Это сводит на нет преимущество обработки исключений, а именно, возможность централизованно обрабатывать ошибки.
Что будет, когда что-то реально выбросит исключение в системе? Мы не используем блоки try
, потому оно будет передано на самый верх стека вызовов. В Node это приводит к прекращению выполнения программы и выводу информации об исключении (вместе с отслеживанием стека) на стандартный вывод.
Поэтому наш сервер будет падать при возникновении проблем в коде – в отличие от проблем с асинхронностью, которые будут переданы как аргументы в функции вызова. Если нам надо обрабатывать все исключения, возникающие при обработке запроса, чтобы мы точно отправили ответ, нам надо добавлять блоки try/catch
в каждом обратном вызове.
Это плохо. Много программ для Node написаны так, чтобы использовать как можно меньше работы с исключениями, подразумевая что в случае возникновения исключения программа не может его обработать, и поэтому надо падать.
Ещё один подход – использование обещаний, которые были описаны в главе 17. Они ловят исключения, выброшенные функциями обратного вызова и передают их как ошибки. В Node можно загрузить библиотеку promise
и использовать её для обработки асинхронных вызовов. Немногие библиотеки Node интегрируют обещания, но обычно их довольно просто обернуть. Отличный модуль “promise”
с NPM содержит функцию denodeify
, которая берёт асинхронную функцию вроде fs.readFile
и преобразовывает её в функцию, возвращающую обещание.
var Promise = require("promise");
var fs = require("fs");
var readFile = Promise.denodeify(fs.readFile);
readFile("file.txt", "utf8").then(function(content) {
console.log("The file contained: " + content);
}, function(error) {
console.log("Failed to read file: " + error);
});
Для сравнения, я написал ещё одну версию файлового сервера с использованием обещаний, которую можно найти на eloquentjavascript.net/code/file_server_promises.js. Она почище, потому что функции теперь могут возвращать результаты, а не назначать обратные вызовы, и исключения передаются неявно.
Приведу несколько строк оттуда, чтобы продемонстрировать разницу в стилях.
Объект fsp
, использующийся в коде, содержит варианты функций fs
с обещаниями, обёрнутыми при помощи Promise.denodeify
. Возвращаемый из обработчика метода объект, со свойствами code
и body
, становится окончательным результатом цепочки обещаний, и он используется для определения того, какой ответ отправить клиенту.
methods.GET = function(path) {
return inspectPath(path).then(function(stats) {
if (!stats) // Does not exist
return {code: 404, body: "File not found"};
else if (stats.isDirectory())
return fsp.readdir(path).then(function(files) {
return {code: 200, body: files.join("\n")};
});
else
return {code: 200,
type: require("mime").lookup(path),
body: fs.createReadStream(path)};
});
};
function inspectPath(path) {
return fsp.stat(path).then(null, function(error) {
if (error.code == "ENOENT") return null;
else throw error;
});
}
Функция inspectPath
– простая обёртка вокруг fs.stat
, обрабатывающая случай, когда файл не найден. В этом случае мы заменяем ошибку на успех, возвращающий null
. Все остальные ошибки можно передавать. Когда обещание, возвращаемое из этих обработчиков, обламывается, сервер отвечает кодом 500.
Итог
Node – отличная простая система, позволяющая запускать JavaScript вне браузера. Изначально она разрабатывалась для работы по сети, чтобы играть роль узла в сети. Но она позволяет делать много всего, и если вы наслаждаетесь программированием на JavaScript, автоматизация ежедневных задач с Node работает отлично.
NPM предоставляет библиотеки для всего, что вам может прийти в голову (и даже для кое-чего, что вам не придёт в голову), и она позволяет скачивать и устанавливать их простой командой. Node также поставляется с набором встроенных модулей, включая “fs”
для работы с файловой системой, и “http”
для запуска HTTP-серверов и создания HTTP-запросов.