Пользователь перетаскивает CSV-файл с заказами в браузерное приложение. Приложение читает его, парсит, показывает предпросмотр данных — без единого запроса к серверу. Или: загрузка аватара с предпросмотром до отправки, проверка типа и размера файла перед загрузкой. Всё это — File и FileReader.
Браузер даёт доступ к файлам пользователя через специальный API. File — это Blob с метаданными (имя, дата). FileReader — асинхронный инструмент для чтения содержимого. Современный способ — использовать методы промисов прямо на File-объекте.
.text(), .arrayBuffer() — промис-based APIФайл приходит из <input type="file"> или Drag & Drop. Можно создать программно:
const file = new File(['Содержимое файла'], 'data.txt', {
type: 'text/plain',
lastModified: Date.now(),
})
console.log(file.name) // 'data.txt'
console.log(file.size) // число байт
console.log(file.type) // 'text/plain'
console.log(file.lastModified) // timestamp Unix
console.log(file instanceof Blob) // true — наследует от Blob!Прямые методы на File/Blob — самый простой способ:
// Читать как текст UTF-8
const text = await file.text()
// Читать как ArrayBuffer (бинарные данные)
const buf = await file.arrayBuffer()
const view = new Uint8Array(buf)Старый способ. Нужен когда требуется отслеживание прогресса или readAsDataURL:
function readAsPromise(file, method = 'readAsText') {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => resolve(e.target.result)
reader.onerror = () => reject(new Error(reader.error.message))
reader.onprogress = e => {
if (e.lengthComputable) {
const pct = Math.round(e.loaded / e.total * 100)
console.log(pct + '%')
}
}
reader[method](file, 'utf-8') // readAsText, readAsDataURL, readAsArrayBuffer
})
}// Создаёт base64-строку: 'data:image/jpeg;base64,/9j/4AA...'
const dataUrl = await new Promise((resolve) => {
const reader = new FileReader()
reader.onload = e => resolve(e.target.result)
reader.readAsDataURL(imageFile)
})
img.src = dataUrl // Показываем предпросмотр сразуАльтернатива без FileReader (быстрее и проще):
const url = URL.createObjectURL(imageFile)
img.src = url
img.onload = () => URL.revokeObjectURL(url)| | FileReader | file.text() / .arrayBuffer() |
|---|---|---|
| API | События (onload) | Promise / async-await |
| Прогресс | Есть (onprogress) | Нет |
| readAsDataURL | Есть | Нет (нужен FileReader или URL.createObjectURL) |
| Отмена | reader.abort() | AbortController |
| Поддержка | Все браузеры | Chrome 76+, Firefox 69+ |
Ошибка 1: Чтение файла до выбора пользователем
// НЕВЕРНО — input.files пуст до события change
const file = input.files[0] // undefined до выбора файла
await file.text() // TypeError!
// ВЕРНО
input.addEventListener('change', async (e) => {
const file = e.target.files[0]
if (!file) return
const text = await file.text()
})Ошибка 2: Не проверять тип и размер
// НЕВЕРНО — принимаем любой файл
const text = await file.text()
// ВЕРНО — валидируем перед чтением
function validateFile(file, maxSizeMB = 5) {
if (!file.type.startsWith('text/') && !file.name.endsWith('.csv')) {
throw new Error('Только текстовые файлы и CSV')
}
if (file.size > maxSizeMB * 1024 * 1024) {
throw new Error(`Файл больше ${maxSizeMB} МБ`)
}
}Ошибка 3: Повторное использование FileReader одновременно
// НЕВЕРНО — один reader не может читать два файла параллельно
const reader = new FileReader()
reader.readAsText(file1)
reader.readAsText(file2) // Прерывает первое чтение!
// ВЕРНО — создаём отдельный reader для каждого файла
const [text1, text2] = await Promise.all([file1.text(), file2.text()])Импорт CSV файлов: парсинг, валидация, предпросмотр и обработка ошибок
// ===== File объект и FileReader =====
// Вспомогательная функция: читает файл через FileReader (старый API)
function readFileWithReader(file, method = 'readAsText') {
return new Promise((resolve, reject) => {
const reader = new FileReader()
const started = Date.now()
reader.onload = (e) => {
const elapsed = Date.now() - started
console.log(` Загружено за ~${elapsed}ms, байт: ${e.loaded}`)
resolve(e.target.result)
}
reader.onerror = () => reject(new Error(`Ошибка: ${reader.error?.message ?? 'неизвестно'}`))
reader.onprogress = (e) => {
if (e.lengthComputable && e.total > 0) {
console.log(` Прогресс: ${Math.round(e.loaded / e.total * 100)}%`)
}
}
if (method === 'readAsText') reader.readAsText(file, 'utf-8')
else if (method === 'readAsDataURL') reader.readAsDataURL(file)
else if (method === 'readAsArrayBuffer') reader.readAsArrayBuffer(file)
})
}
// Валидация файла до чтения
function validateCSVFile(file) {
const errors = []
const MAX_SIZE = 5 * 1024 * 1024 // 5 MB
if (!file.type.includes('csv') && !file.name.endsWith('.csv')) {
errors.push('Ожидается CSV файл')
}
if (file.size === 0) {
errors.push('Файл пустой')
}
if (file.size > MAX_SIZE) {
errors.push(`Файл слишком большой: ${(file.size / 1024 / 1024).toFixed(1)} МБ (макс. 5 МБ)`)
}
return { valid: errors.length === 0, errors }
}
// Парсер CSV
function parseCSV(text) {
const lines = text.split('\n').filter(l => l.trim() !== '')
if (lines.length === 0) return { headers: [], records: [], count: 0 }
const headers = lines[0].split(',').map(h => h.trim())
const records = lines.slice(1).map(line => {
const values = line.split(',').map(v => v.trim())
return Object.fromEntries(headers.map((h, i) => [h, values[i] ?? '']))
})
return { headers, records, count: records.length }
}
// ===== Тесты =====
async function runDemo() {
// --- 1. Метаданные файла ---
console.log('=== File метаданные ===')
const csvContent = [
'id,name,email,amount',
'1,Иван Петров,ivan@example.com,1500.00',
'2,Мария Сидорова,maria@example.com,2300.50',
'3,Алексей Козлов,alexey@example.com,850.00',
'4,Ольга Смирнова,olga@example.com,4200.00',
].join('\n')
const csvFile = new File([csvContent], 'payments.csv', {
type: 'text/csv',
lastModified: Date.now() - 86400000, // вчера
})
console.log('name:', csvFile.name)
console.log('size:', csvFile.size, 'байт')
console.log('type:', csvFile.type)
console.log('lastModified:', new Date(csvFile.lastModified).toLocaleDateString('ru'))
console.log('instanceof Blob:', csvFile instanceof Blob) // true
// --- 2. Валидация ---
console.log('\n=== Валидация файла ===')
const { valid, errors } = validateCSVFile(csvFile)
console.log('Валидный:', valid) // true
console.log('Ошибки:', errors) // []
const badFile = new File([''], 'test.jpg', { type: 'image/jpeg' })
const { valid: badValid, errors: badErrors } = validateCSVFile(badFile)
console.log('\nbadFile валидный:', badValid) // false
console.log('Ошибки:', badErrors) // ['Ожидается CSV файл', 'Файл пустой']
// --- 3. Современный API: file.text() ---
console.log('\n=== Современный API: file.text() ===')
const text = await csvFile.text()
const result = parseCSV(text)
console.log('Заголовки:', result.headers)
console.log('Записей:', result.count)
console.log('Первый:', result.records[0].name, '—', result.records[0].amount)
// --- 4. FileReader: читаем как текст ---
console.log('\n=== FileReader (старый API) ===')
const frText = await readFileWithReader(csvFile, 'readAsText')
console.log('Результат идентичен file.text():', frText === text) // true
// --- 5. readAsDataURL: base64 ---
console.log('\n=== FileReader: readAsDataURL ===')
// Симулируем GIF (magic bytes: 47 49 46 38 39 61)
const gifFile = new File(
[new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])],
'test.gif',
{ type: 'image/gif' }
)
const dataUrl = await readFileWithReader(gifFile, 'readAsDataURL')
console.log('DataURL:', dataUrl.substring(0, 40) + '...')
// data:image/gif;base64,R0lGODlh...
console.log('Prefix:', dataUrl.split(';')[0]) // data:image/gif
console.log('Encoding:', dataUrl.split(';')[1].split(',')[0]) // base64
// --- 6. Параллельное чтение нескольких файлов ---
console.log('\n=== Параллельное чтение файлов ===')
const files = [
new File(['файл 1 данные'], 'f1.txt', { type: 'text/plain' }),
new File(['файл 2 данные'], 'f2.txt', { type: 'text/plain' }),
new File(['файл 3 данные'], 'f3.txt', { type: 'text/plain' }),
]
// Promise.all читает все файлы параллельно
const contents = await Promise.all(files.map(f => f.text()))
contents.forEach((content, i) => {
console.log(`files[${i}] (${files[i].name}): "${content}"`)
})
// --- 7. Анализ CSV: итоговая сумма ---
console.log('\n=== Анализ платежей ===')
const total = result.records.reduce((sum, r) => sum + parseFloat(r.amount || 0), 0)
const max = result.records.reduce((m, r) => Math.max(m, parseFloat(r.amount || 0)), 0)
console.log('Итого платежей:', total.toFixed(2), 'руб.')
console.log('Максимальный платёж:', max.toFixed(2), 'руб.')
}
runDemo()Пользователь перетаскивает CSV-файл с заказами в браузерное приложение. Приложение читает его, парсит, показывает предпросмотр данных — без единого запроса к серверу. Или: загрузка аватара с предпросмотром до отправки, проверка типа и размера файла перед загрузкой. Всё это — File и FileReader.
Браузер даёт доступ к файлам пользователя через специальный API. File — это Blob с метаданными (имя, дата). FileReader — асинхронный инструмент для чтения содержимого. Современный способ — использовать методы промисов прямо на File-объекте.
.text(), .arrayBuffer() — промис-based APIФайл приходит из <input type="file"> или Drag & Drop. Можно создать программно:
const file = new File(['Содержимое файла'], 'data.txt', {
type: 'text/plain',
lastModified: Date.now(),
})
console.log(file.name) // 'data.txt'
console.log(file.size) // число байт
console.log(file.type) // 'text/plain'
console.log(file.lastModified) // timestamp Unix
console.log(file instanceof Blob) // true — наследует от Blob!Прямые методы на File/Blob — самый простой способ:
// Читать как текст UTF-8
const text = await file.text()
// Читать как ArrayBuffer (бинарные данные)
const buf = await file.arrayBuffer()
const view = new Uint8Array(buf)Старый способ. Нужен когда требуется отслеживание прогресса или readAsDataURL:
function readAsPromise(file, method = 'readAsText') {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => resolve(e.target.result)
reader.onerror = () => reject(new Error(reader.error.message))
reader.onprogress = e => {
if (e.lengthComputable) {
const pct = Math.round(e.loaded / e.total * 100)
console.log(pct + '%')
}
}
reader[method](file, 'utf-8') // readAsText, readAsDataURL, readAsArrayBuffer
})
}// Создаёт base64-строку: 'data:image/jpeg;base64,/9j/4AA...'
const dataUrl = await new Promise((resolve) => {
const reader = new FileReader()
reader.onload = e => resolve(e.target.result)
reader.readAsDataURL(imageFile)
})
img.src = dataUrl // Показываем предпросмотр сразуАльтернатива без FileReader (быстрее и проще):
const url = URL.createObjectURL(imageFile)
img.src = url
img.onload = () => URL.revokeObjectURL(url)| | FileReader | file.text() / .arrayBuffer() |
|---|---|---|
| API | События (onload) | Promise / async-await |
| Прогресс | Есть (onprogress) | Нет |
| readAsDataURL | Есть | Нет (нужен FileReader или URL.createObjectURL) |
| Отмена | reader.abort() | AbortController |
| Поддержка | Все браузеры | Chrome 76+, Firefox 69+ |
Ошибка 1: Чтение файла до выбора пользователем
// НЕВЕРНО — input.files пуст до события change
const file = input.files[0] // undefined до выбора файла
await file.text() // TypeError!
// ВЕРНО
input.addEventListener('change', async (e) => {
const file = e.target.files[0]
if (!file) return
const text = await file.text()
})Ошибка 2: Не проверять тип и размер
// НЕВЕРНО — принимаем любой файл
const text = await file.text()
// ВЕРНО — валидируем перед чтением
function validateFile(file, maxSizeMB = 5) {
if (!file.type.startsWith('text/') && !file.name.endsWith('.csv')) {
throw new Error('Только текстовые файлы и CSV')
}
if (file.size > maxSizeMB * 1024 * 1024) {
throw new Error(`Файл больше ${maxSizeMB} МБ`)
}
}Ошибка 3: Повторное использование FileReader одновременно
// НЕВЕРНО — один reader не может читать два файла параллельно
const reader = new FileReader()
reader.readAsText(file1)
reader.readAsText(file2) // Прерывает первое чтение!
// ВЕРНО — создаём отдельный reader для каждого файла
const [text1, text2] = await Promise.all([file1.text(), file2.text()])Импорт CSV файлов: парсинг, валидация, предпросмотр и обработка ошибок
// ===== File объект и FileReader =====
// Вспомогательная функция: читает файл через FileReader (старый API)
function readFileWithReader(file, method = 'readAsText') {
return new Promise((resolve, reject) => {
const reader = new FileReader()
const started = Date.now()
reader.onload = (e) => {
const elapsed = Date.now() - started
console.log(` Загружено за ~${elapsed}ms, байт: ${e.loaded}`)
resolve(e.target.result)
}
reader.onerror = () => reject(new Error(`Ошибка: ${reader.error?.message ?? 'неизвестно'}`))
reader.onprogress = (e) => {
if (e.lengthComputable && e.total > 0) {
console.log(` Прогресс: ${Math.round(e.loaded / e.total * 100)}%`)
}
}
if (method === 'readAsText') reader.readAsText(file, 'utf-8')
else if (method === 'readAsDataURL') reader.readAsDataURL(file)
else if (method === 'readAsArrayBuffer') reader.readAsArrayBuffer(file)
})
}
// Валидация файла до чтения
function validateCSVFile(file) {
const errors = []
const MAX_SIZE = 5 * 1024 * 1024 // 5 MB
if (!file.type.includes('csv') && !file.name.endsWith('.csv')) {
errors.push('Ожидается CSV файл')
}
if (file.size === 0) {
errors.push('Файл пустой')
}
if (file.size > MAX_SIZE) {
errors.push(`Файл слишком большой: ${(file.size / 1024 / 1024).toFixed(1)} МБ (макс. 5 МБ)`)
}
return { valid: errors.length === 0, errors }
}
// Парсер CSV
function parseCSV(text) {
const lines = text.split('\n').filter(l => l.trim() !== '')
if (lines.length === 0) return { headers: [], records: [], count: 0 }
const headers = lines[0].split(',').map(h => h.trim())
const records = lines.slice(1).map(line => {
const values = line.split(',').map(v => v.trim())
return Object.fromEntries(headers.map((h, i) => [h, values[i] ?? '']))
})
return { headers, records, count: records.length }
}
// ===== Тесты =====
async function runDemo() {
// --- 1. Метаданные файла ---
console.log('=== File метаданные ===')
const csvContent = [
'id,name,email,amount',
'1,Иван Петров,ivan@example.com,1500.00',
'2,Мария Сидорова,maria@example.com,2300.50',
'3,Алексей Козлов,alexey@example.com,850.00',
'4,Ольга Смирнова,olga@example.com,4200.00',
].join('\n')
const csvFile = new File([csvContent], 'payments.csv', {
type: 'text/csv',
lastModified: Date.now() - 86400000, // вчера
})
console.log('name:', csvFile.name)
console.log('size:', csvFile.size, 'байт')
console.log('type:', csvFile.type)
console.log('lastModified:', new Date(csvFile.lastModified).toLocaleDateString('ru'))
console.log('instanceof Blob:', csvFile instanceof Blob) // true
// --- 2. Валидация ---
console.log('\n=== Валидация файла ===')
const { valid, errors } = validateCSVFile(csvFile)
console.log('Валидный:', valid) // true
console.log('Ошибки:', errors) // []
const badFile = new File([''], 'test.jpg', { type: 'image/jpeg' })
const { valid: badValid, errors: badErrors } = validateCSVFile(badFile)
console.log('\nbadFile валидный:', badValid) // false
console.log('Ошибки:', badErrors) // ['Ожидается CSV файл', 'Файл пустой']
// --- 3. Современный API: file.text() ---
console.log('\n=== Современный API: file.text() ===')
const text = await csvFile.text()
const result = parseCSV(text)
console.log('Заголовки:', result.headers)
console.log('Записей:', result.count)
console.log('Первый:', result.records[0].name, '—', result.records[0].amount)
// --- 4. FileReader: читаем как текст ---
console.log('\n=== FileReader (старый API) ===')
const frText = await readFileWithReader(csvFile, 'readAsText')
console.log('Результат идентичен file.text():', frText === text) // true
// --- 5. readAsDataURL: base64 ---
console.log('\n=== FileReader: readAsDataURL ===')
// Симулируем GIF (magic bytes: 47 49 46 38 39 61)
const gifFile = new File(
[new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])],
'test.gif',
{ type: 'image/gif' }
)
const dataUrl = await readFileWithReader(gifFile, 'readAsDataURL')
console.log('DataURL:', dataUrl.substring(0, 40) + '...')
// data:image/gif;base64,R0lGODlh...
console.log('Prefix:', dataUrl.split(';')[0]) // data:image/gif
console.log('Encoding:', dataUrl.split(';')[1].split(',')[0]) // base64
// --- 6. Параллельное чтение нескольких файлов ---
console.log('\n=== Параллельное чтение файлов ===')
const files = [
new File(['файл 1 данные'], 'f1.txt', { type: 'text/plain' }),
new File(['файл 2 данные'], 'f2.txt', { type: 'text/plain' }),
new File(['файл 3 данные'], 'f3.txt', { type: 'text/plain' }),
]
// Promise.all читает все файлы параллельно
const contents = await Promise.all(files.map(f => f.text()))
contents.forEach((content, i) => {
console.log(`files[${i}] (${files[i].name}): "${content}"`)
})
// --- 7. Анализ CSV: итоговая сумма ---
console.log('\n=== Анализ платежей ===')
const total = result.records.reduce((sum, r) => sum + parseFloat(r.amount || 0), 0)
const max = result.records.reduce((m, r) => Math.max(m, parseFloat(r.amount || 0)), 0)
console.log('Итого платежей:', total.toFixed(2), 'руб.')
console.log('Максимальный платёж:', max.toFixed(2), 'руб.')
}
runDemo()Ты разрабатываешь модуль импорта данных. Нужно реализовать несколько утилит для работы с файлами. Реализуй: - `getFileInfo(file)` — возвращает `{ name, size, type, sizeKB }` (`sizeKB` — размер в КБ, округлённый до 2 знаков) - `processCSV(file)` — читает File через `file.text()`, парсит CSV (первая строка — заголовки, разделитель — запятая), возвращает Promise с массивом объектов - `detectFileType(file)` — читает первые 4 байта через `file.slice(0, 4).arrayBuffer()` и определяет тип: `'PNG'`, `'JPEG'`, `'PDF'` или `'UNKNOWN'` по magic bytes
getFileInfo: { name: file.name, size: file.size, type: file.type, sizeKB: Math.round(file.size/1024*100)/100 }. processCSV: await file.text(), split("\n").filter(), slice(1).map(). detectFileType: file.slice(0,4).arrayBuffer(), new Uint8Array(buf), проверяй байты