File и FileReader
Объект File наследуется от объекта Blob и обладает возможностями по взаимодействию с файловой системой.
Есть два способа его получить.
Во-первых, есть конструктор, похожий на Blob :
new File(fileParts, fileName, [options])
- fileParts – массив значений Blob / BufferSource /строки.
- fileName – имя файла, строка.
- options – необязательный объект со свойством:
- lastModified – дата последнего изменения в формате таймстамп (целое число).
Во-вторых, чаще всего мы получаем файл из или через перетаскивание с помощью мыши, или из других интерфейсов браузера. В этом случае файл получает эту информацию из ОС.
Так как File наследует от Blob , у объектов File есть те же свойства плюс:
В этом примере мы получаем объект File из :
Через можно выбрать несколько файлов, поэтому input.files – псевдомассив выбранных файлов. Здесь у нас только один файл, поэтому мы просто берём input.files[0] .
FileReader
FileReader объект, цель которого читать данные из Blob (и, следовательно, из File тоже).
Данные передаются при помощи событий, так как чтение с диска может занять время.
let reader = new FileReader(); // без аргументов
- readAsArrayBuffer(blob) – считать данные как ArrayBuffer
- readAsText(blob, [encoding]) – считать данные как строку (кодировка по умолчанию: utf-8 )
- readAsDataURL(blob) – считать данные как base64-кодированный URL.
- abort() – отменить операцию.
Выбор метода для чтения зависит от того, какой формат мы предпочитаем, как мы хотим далее использовать данные.
- readAsArrayBuffer – для бинарных файлов, для низкоуровневой побайтовой работы с бинарными данными. Для высокоуровневых операций у File есть свои методы, унаследованные от Blob , например, slice , мы можем вызвать их напрямую.
- readAsText – для текстовых файлов, когда мы хотим получить строку.
- readAsDataURL – когда мы хотим использовать данные в src для img или другого тега. Есть альтернатива – можно не читать файл, а вызвать URL.createObjectURL(file) , детали в главе Blob.
В процессе чтения происходят следующие события:
- loadstart – чтение начато.
- progress – срабатывает во время чтения данных.
- load – нет ошибок, чтение окончено.
- abort – вызван abort() .
- error – произошла ошибка.
- loadend – чтение завершено (успешно или нет).
Когда чтение закончено, мы сможем получить доступ к его результату следующим образом:
Наиболее часто используемые события – это, конечно же, load и error .
Как упоминалось в главе Blob, FileReader работает для любых объектов Blob, а не только для файлов.
Поэтому мы можем использовать его для преобразования Blob в другой формат:
- readAsArrayBuffer(blob) – в ArrayBuffer ,
- readAsText(blob, [encoding]) – в строку (альтернатива TextDecoder ),
- readAsDataURL(blob) – в формат base64-кодированного URL.
Для веб-воркеров доступен синхронный вариант FileReader , именуемый FileReaderSync.
Его методы считывания read* не генерируют события, а возвращают результат, как это делают обычные функции.
Но это только внутри веб-воркера, поскольку задержки в синхронных вызовах, которые возможны при чтении из файла, в веб-воркерах менее важны. Они не влияют на страницу.
Итого
File объекты наследуют от Blob .
Помимо методов и свойств Blob , объекты File также имеют свойства name и lastModified плюс внутреннюю возможность чтения из файловой системы. Обычно мы получаем объекты File из пользовательского ввода, например, через или перетаскиванием с помощью мыши, в событии dragend .
Объекты FileReader могут читать из файла или Blob в одном из трёх форматов:
- Строка ( readAsText ).
- ArrayBuffer ( readAsArrayBuffer ).
- URL в формате base64 ( readAsDataURL ).
Однако, во многих случаях нам не нужно читать содержимое файла. Как и в случае с Blob, мы можем создать короткий URL с помощью URL.createObjectURL(file) и использовать его в теге или . Таким образом, файл может быть загружен или показан в виде изображения, как часть canvas и т.д.
А если мы собираемся отправить File по сети, то это также легко, поскольку в сетевые методы, такие как XMLHttpRequest или fetch , встроена возможность отсылки File .
JavaScript: чтение и запись файлов с помощью File System Access API
В этой небольшой статье я хочу рассказать вам о File System Access API (далее — FSA ), позволяющем читать и записывать файлы в локальную систему пользователя с помощью браузера.
Если вам это интересно, прошу под кат.
К сожалению, на сегодняшний день FSA поддерживается только 34.68% браузеров: сюда входят все десктопные браузеры, за исключением Firefox .
Возможности
FSA расширяет объект window следующими методами:
- showOpenFilePicker — для чтения файлов;
- showSaveFilePicker — для записи файлов;
- showDirectoryPicker — для чтения директории.
Данные методы называются фабриками дескрипторов локальной файловой системы (local file system handle factories) и возвращают FileSystemHandle — сущность (entity) для работы с файлами ( FileSystemFileHandle ) или директориями ( FileSystemDirectoryHandle ), соответственно.
FileSystemHandle содержит поле kind (вид, тип), значением которого может быть либо file , либо directory , и поле name (название файла или директории).
Чтение файла
Для получения сущности для чтения файла ( FileSystemFileHandle ) используется метод showOpenFilePicker :
const [fileHandle] = await window.showOpenFilePicker(options)
Общими для showOpenFilePicker и showSaveFilePicker являются настройки:
- types?: object — разрешенные типы файлов;
- excludeAcceptAllOption?: boolean — если имеет значение true , picker будет принимать/сохранять только файлы с типами, определенными в types .
Значением поля types является объект со следующими свойствами:
Специфичной для showOpenFilePicker настройкой является multiple?: boolean — если имеет значение true , picker будет принимать несколько файлов и возвращать массив FileSystemFileHandle .
Для чтения содержимого файла с помощью FileSystemFileHandle используется метод getFile :
const file = await fileHandle.getFile()
getFile возвращает интерфейс File . Для преобразования blob в текст можно использовать метод text (данный метод наследуется File от интерфейса Blob ):
const fileContent = await file.text()
Предположим, что у нас имеется директория fsa-test , в которой лежит файл test.txt с текстом Hi World . Прочитаем этот файл.
Пользователь должен явно выразить намерение прочитать файл или директорию, например, нажать кнопку.
const filePicker$ = document.querySelector('.file-picker') filePicker$.addEventListener('click', async () => < const [fileHandle] = await window.showOpenFilePicker() const file = await fileHandle.getFile() const fileContent = await file.text() console.log(fileContent) >)
Нажимаем на кнопку. Выбираем файл test.txt . Получаем Hi World в консоли.
Создадим еще парочку файлов, например, test2.txt с текстом Bye World и test3.txt с текстом Hi World Once Again .
Прочитаем все 3 файла, запретив пользователю выбирать другие файлы.
// настройки const options = < // можно выбирать несколько файлов multiple: true, // разрешенный тип файлов types: [ < description: 'Text', accept: < 'text/plain': '.txt' // разрешаем изображения // 'image/*': ['.jpg', '.jpeg', '.png', '.gif'] >> ], // можно выбирать только разрешенные файлы // по моим наблюдениям, данная настройка работает не совсем корректно excludeAcceptAllOption: true > filePicker$.addEventListener('click', async () => < const fileHandles = await window.showOpenFilePicker(options) const allFilesContent = await Promise.all( fileHandles.map(async (fileHandle) =>< const file = await fileHandle.getFile() const fileContent = await file.text() return fileContent >) ) console.log(allFilesContent.join('\n')) >)
Нажимаем на кнопку. Выбираем файлы test.txt , test2.txt и test3.txt . Получаем в консоли:
Hi World Bye World Hi World Once Again
Запись файлов
Для получения сущности для записи файла ( FileSystemFileHandle ) используется метод showSaveFilePicker :
const fileHandle = await window.showSaveFilePicker(options)
Специфичной для showSaveFilePicker является настройка suggestedName?: string — рекомендуемое название создаваемого файла.
Для записи файла с помощью FileSystemFileHandle используется метод createWritable :
const writableStream = await fileHandle.createWritable(options)
Единственной доступной на сегодняшний день настройкой createWritable является keepExistingData?: boolean — если имеет значение true , picker сохраняет данные, имеющиеся в файле на момент записи, в противном случае, содержимое файла перезаписывается.
createWritable возвращает FileSystemWritableFileStream , предоставляющий метод write для записи файла:
await writableStream.write(fileData)
fileData — это данные для записи.
Запишем файл test4.txt с текстом Bye World Once Again .
const fileSaver$ = document.querySelector('.file-saver') // настройки const options = < // рекомендуемое название файла suggestedName: 'test4.txt', types: [ < description: 'Text', accept: < 'text/plain': '.txt' >> ], excludeAcceptAllOption: true > // данные для записи const fileData = 'Bye World Once Again' fileSaver$.addEventListener('click', async () => < const fileHandle = await window.showSaveFilePicker(options) const writableStream = await fileHandle.createWritable() await writableStream.write(fileData) // данный метод не упоминается в черновике спецификации, // хотя там говорится о необходимости закрытия потока // для успешной записи файла await writableStream.close() >)
Нажимаем на кнопку File saver . Сохраняем файл. Видим, что в директории появился файл test4.txt с текстом Bye World Once Again .
Перезапишем содержимое файла test.txt .
const filePicker$ = document.querySelector('.file-picker') const fileSaver$ = document.querySelector('.file-saver') const fileData = 'Bye World' let fileHandle filePicker$.addEventListener('click', async () => < ;[fileHandle] = await window.showOpenFilePicker() const file = await fileHandle.getFile() const fileContent = await file.text() console.log(fileContent) >) fileSaver$.addEventListener('click', async () => < const writableStream = await fileHandle.createWritable(< // не работает! keepExistingData: true >) await writableStream.write(fileData) await writableStream.close() >)
Нажимаем на кнопку File picker . Выбираем файл test.txt . Нажимаем на кнопку File saver . Получаем уведомление о том, что браузер сможет манипулировать файлом test.txt до закрытия всех вкладок. Нажимаем Сохранить . Открываем test.txt . Видим, что текст Hi World изменился на Bye World (текст Hi World должен был сохраниться, поскольку мы указали настройку keepExistingData: true ).
Чтение директории
Для получения сущности для чтения директории ( FileSystemDirectoryHandle ) используется метод showDirectoryPicker :
const dirPicker = await window.showDirectoryPicker()
Для перебора содержимого выбранной директории можно использовать следующие методы:
- entries — возвращает массив массивов вида [name, handle] , где name — название сущности, а handle — FileSystemHandle ;
- values — возвращает массив handle ;
- keys — возвращает массив name .
Переместим файлы test2.txt , test3.txt и test4.txt в директорию text и прочитаем содержимое директории fsa-test .
Структура директории fsa-test :
- index.html - script.js - test.txt - text - test2.txt - test3.txt - test4.txt
const dirPicker$ = document.querySelector('.dir-picker') dirPicker$.addEventListener('click', async () => < const dirHandle = await window.showDirectoryPicker() for await (const entry of dirHandle.values()) < console.log(entry.kind, entry.name) >>)
Нажимаем на кнопку Directory picker . Выбираем директорию fsa-test . Получаем уведомление о том, что наш сайт сможет просматривать файлы в выбранной директории. Нажимаем Просмотреть файлы . Получаем в консоли:
file index.html file script.js file test.txt directory text
Для получения FileSystemFileHandle и FileSystemDirectoryHandle , находящихся внутри выбранной директории предназначены методы getFileHandle и getDirectoryHandle , соответственно. Обязательным параметром, принимаемым этими методами, является name — название файла/директории.
Прочитаем содержимое файла test.txt и директории text .
const dirPicker$ = document.querySelector('.dir-picker') dirPicker$.addEventListener('click', async () => < const dirHandle = await window.showDirectoryPicker() const fileHandle = await dirHandle.getFileHandle('test.txt') // читаем содержимое файла `test.txt` const fileContent = await (await fileHandle.getFile()).text() console.log(fileContent) // читаем содержимое директории `text` const subDirHandle = await dirHandle.getDirectoryHandle('text') for await (const handle of subDirHandle.values()) < console.log(handle.kind, handle.name) >>)
Нажимаем на кнопку Directory picker . Выбираем директорию fsa-test . Нажимаем Просмотреть файлы . Получаем в консоли:
Bye World file test4.txt file test2.txt file test3.txt
getFileHandle и getDirectoryHandle также принимают настройку create?: boolean — если имеет значение true , запрашиваемый файл/директория создается при отсутствии:
const fileHandle = await dirHandle.getFileHandle('test5.txt', < create: true >)
Удаление файла/директории
Для удаления файла или директории предназначен метод FileSystemDirectoryHandle.removeEntry . Он принимает 2 параметра:
- name: string — название удаляемого файла/директории;
- options? — объект с настройками:
- recursive: boolean — если имеет значение true , удаляется сама директория и все ее содержимое (данная настройка позволяет удалять непустые директории).
Удалим файл test.txt и директорию text :
const dirPicker$ = document.querySelector('.dir-picker') dirPicker$.addEventListener('click', async () => < const dirHandle = await window.showDirectoryPicker() // удаляем файл `test.txt` await dirHandle.removeEntry('test.txt') // удаляем директорию `text` // в ней содержатся файлы `test2.txt`, `test3.txt` и `text4.txt`, // т.е. она является непустой await dirHandle.removeEntry('text', < recursive: true >) >)
Нажимаем на кнопку Directory picker . Выбираем директорию fsa-test . Получаем сразу 2 уведомления от браузера. Предоставляем ему необходимые разрешения. Видим, что файл test.txt и директория text благополучно удаляются.
Как видите, FSA предоставляет в наше распоряжение довольно интересные возможности по работе с файлами и директориями, находящимися в локальной системе пользователя. Фактически он представляет собой урезанную версию модуля fs для браузера.
Полагаю, с ростом поддержки FSA найдет широкое применение в веб-разработке и станет прекрасным дополнением набора инструментов, включающих File API , input type=»file» и Drag and drop API .
Пожалуй, это все, чем я хотел поделиться с вами в этой статье.
Обратите внимание: мы рассмотрели далеко не все возможности, предоставляемые FSA , поэтому рекомендую полистать спецификацию.
Благодарю за внимание и happy coding!