Одна из самых распространенных вещей, которые вы захотите сделать практически с любым языком программирования, - это открыть и прочитать файл. С большинством языков это довольно просто, но для ветеранов JavaScript это может показаться немного странным. В течение стольких лет JavaScript был доступен только в браузере, поэтому интерфейсные разработчики могут быть знакомы только с FileReader API или аналогичным.
Node.js, как вы, наверное, знаете, сильно отличается от обычного JavaScript в браузере. Он имеет собственный набор библиотек, предназначенных для обработки задач ОС и файловой системы, таких как открытие и чтение файлов. В этой статье я покажу вам, как использовать Node.js для чтения файлов. В частности, для этого мы будем использовать модуль fs.
Есть два способа открыть и прочитать файл с помощью модуля fs
- Загрузить все содержимое сразу (буферизация)
- Поэтапная загрузка содержимого (потоковая передача)
Каждый из этих методов будет объяснен в следующих двух разделах.
Буферизация содержимого с помощью fs.readFile
Это наиболее распространенный способ чтения файла с помощью Node.js, особенно для начинающих, благодаря его простоте и удобству. Хотя, как вы поймете в следующем разделе, он не обязательно самый лучший или самый эффективный.
Вот быстрый пример использования fs.readFile :
var fs = require('fs');
fs.readFile('my-file.txt', 'utf8', function(err, data) {
if (err) throw err;
console.log(data);
});
data
для обратного вызова содержит полное содержимое файла,
представленного в виде строки в формате utf8
Если вы полностью
опустите utf8
, метод просто вернет необработанное содержимое в
объекте Buffer. Удалив utf8
в
приведенном выше коде (и предполагая, что my-file.txt содержит строку
«Привет!»), Мы получим следующий вывод:
$ node read-file.js
<Buffer 48 65 79 20 74 68 65 72 65 21>
Вы могли заметить, что fs.readFile
, что означает, что этот метод
выполняется асинхронно. Это следует использовать всякий раз, когда это
возможно, чтобы избежать блокировки основного потока выполнения, но
иногда вам нужно делать что-то синхронно, и в этом случае Node
предоставляет вам метод
readFileSync.
Этот метод работает точно так же, за исключением того, что содержимое файла возвращается непосредственно из вызова функции, а поток выполнения блокируется, пока он загружает файл. Я обычно использую это в разделах запуска моих программ (например, когда мы загружаем файлы конфигурации) или в приложениях командной строки, где блокировка основного потока не имеет большого значения.
Вот как загрузить файл синхронно с Node:
var fs = require('fs');
try {
var data = fs.readFileSync('my-file.txt', 'utf8');
console.log(data);
} catch(e) {
console.log('Error:', e.stack);
}
Обратите внимание, что с блокирующим (синхронным) вызовом мы должны
использовать try...catch
для обработки любых ошибок, в отличие от
неблокирующей (асинхронной) версии, где ошибки просто передавались нам в
качестве аргументов.
За исключением способа, которым эти методы возвращают данные и обрабатывают ошибки, они работают почти так же.
Потоковая передача содержимого с помощью fs.createReadStream
Второй способ открыть и прочитать файл - открыть его как Stream с помощью метода fs.createReadStream. Все потоки Node являются экземплярами объекта EventEmitter , что позволяет вам подписываться на важные события.
Читаемый объект потока может быть полезен по множеству причин, некоторые из которых включают:
- Меньший объем памяти . Поскольку данные целевого файла загружаются кусками, не так много памяти требуется для хранения данных в буфере.
- Более быстрое время отклика . Для приложений, чувствительных ко времени, время между запросом и ответом имеет решающее значение. Потоки сокращают время отклика (особенно для больших файлов), поскольку им не нужно ждать загрузки всего файла перед возвратом данных.
- Данные трубопроводов . Абстракция потока позволяет вам использовать общий интерфейс между производителями и потребителями данных для передачи этих данных по каналам. Это очень похоже на концепцию конвейера Unix.
Хотя на самом деле использовать потоки не так уж сложно, они могут быть
немного пугающими и не столь интуитивно понятны, как метод fs.readFile
Вот привет, мир потоковой передачи файлов:
var fs = require('fs');
var data = '';
var readStream = fs.createReadStream('my-file.txt', 'utf8');
readStream.on('data', function(chunk) {
data += chunk;
}).on('end', function() {
console.log(data);
});
Этот код делает в точности то же самое, что и код в первом разделе, за исключением того, что мы должны «собирать» блоки данных перед их выводом на консоль. Если ваш файл довольно маленький, вы, вероятно, когда-либо получите только один фрагмент, но для больших файлов, таких как аудио и видео, вам придется собирать несколько фрагментов. Это тот случай, когда вы начнете замечать настоящую ценность потоковых файлов.
Обратите внимание, что пример, который я показал выше, в основном противоречит цели использования потока, поскольку мы все равно собираем данные в буфере (переменной), но, по крайней мере, он дает вам представление о том, как они работают. Лучший пример, показывающий сильные стороны файловых потоков, можно увидеть здесь, в маршруте Express, который обрабатывает запрос файла:
var fs = require('fs');
var path = require('path');
var http = require('http');
var staticBasePath = './static';
var staticServe = function(req, res) {
var fileLoc = path.resolve(staticBasePath);
fileLoc = path.join(fileLoc, req.url);
var stream = fs.createReadStream(fileLoc);
stream.on('error', function(error) {
res.writeHead(404, 'Not Found');
res.end();
});
stream.pipe(res);
};
var httpServer = http.createServer(staticServe);
httpServer.listen(8080);
Все, что мы здесь делаем, это открываем файл с помощью
fs.createReadStream
и перенаправляем его объекту ответа res
. Мы
даже можем подписаться на сообщения error
и обрабатывать их по мере их
возникновения. Это гораздо лучший метод работы с файлами, если вы
научитесь его правильно использовать. Чтобы получить более полный пример
и объяснение приведенного выше кода, ознакомьтесь с этой статьей о
создании статических файловых серверов с помощью
Node .
Заключение
Из этой статьи вы должны были изучить основы чтения файлов, а также некоторые расширенные методы загрузки с использованием объектов Stream. Ключевым моментом является знание того, когда их использовать, и это следует тщательно продумать для приложений с ограниченным объемом памяти или временем.
Какой метод работы с файлами вы предпочитаете? Как вы использовали Streams в прошлом? Дайте нам знать об этом в комментариях!