Ответ 1
Я не вижу явных утечек памяти или вещей, которые я могу изменить, чтобы помочь вывоз мусора. Я храню идентификаторы блоков в массиве, очевидно будет некоторая память, но это не должно быть массивным. Это почти так, как будто File API содержит весь файл, который он разрезает на память.
Вы правы. Новый Blob
, созданный .slice()
, хранится в памяти.
Решение заключается в вызове Blob.prototype.close()
в ссылке Blob
при завершении обработки объекта Blob
или File
.
Обратите внимание, что в javascript
в Question также создается новый экземпляр FileReader
, если функция upload
вызывается более одного раза.
Метод
slice()
возвращает новый объектBlob
с размером байтов от необязательного параметраstart
до, но не включая необязательный параметрend
и атрибутtype
, который является значение необязательного параметраcontentType
.
Blob
экземпляры существуют в течение document
. Хотя Blob
следует собирать мусор после удаления из Blob URL Store
9.6. Время жизни URL-адресов Blob
Примечание. Пользовательские агенты могут мусор собирать ресурсы, удаленные из
Blob URL Store
.
Каждый
Blob
должен иметь внутреннее состояниеStructuredClone
. Дальнейшее нормативное определение состояния моментального снимка может дляFile
s.
Метод
close()
называетсяclose
aBlob
и должен действовать как следующим образом:
- Если
readability state
контекстного объектаCLOSED
, прервите этот алгоритм.- В противном случае установите
readability state
вcontext object
наCLOSED
.- Если объект контекста имеет запись в
Blob URL Store
, удалите запись, соответствующуюcontext object
.
Если объект Blob
передан в URL.createObjectURL()
, вызовите URL.revokeObjectURL()
на Blob
или File
объект, затем вызовите .close()
.
revokeObjectURL(url)
статический методОтменяет
Blob URL
, указанный в строкеurl
, удалив соответствующую запись из Хранилища URL-адресов Blob. Этот метод должен действовать следующим образом: 1. Еслиurl
относится кBlob
, у которого естьreadability state
ofCLOSED
ИЛИ, если значение, предоставленное для Аргументurl
а неBlob URL
, ИЛИ, если значение, предоставленное для аргументаurl
, не имеет записи вBlob URL Store
, этот вызов метода делает ничего. Пользовательские агенты могут отображать сообщение на консоли ошибок. 2. В противном случае пользовательские агенты должныremove the entry
изBlob URL Store
дляurl
.
Вы можете просмотреть результат этих вызовов, открыв
chrome://blob-internals
просмотр деталей до и после вызовов, которые создают Blob
и закрыть Blob
.
Например, из
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Refcount: 1
Content Type: text/plain
Type: data
Length: 3
to
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Refcount: 1
Content Type: text/plain
после вызова .close()
. Аналогично из
blob:http://example.com/c2823f75-de26-46f9-a4e5-95f57b8230bd
Uuid: 29e430a6-f093-40c2-bc70-2b6838a713bc
Альтернативным подходом может быть отправка файла в виде ArrayBuffer
или фрагментов буферов массивов. Затем повторно соберите файл на сервере.
Или вы можете вызывать FileReader
конструктор, FileReader.prototype.readAsArrayBuffer()
и load
событие FileReader
каждый раз.
В load
событие FileReader
пройдет ArrayBuffer
до Uint8Array
, используйте ReadableStream
, TypedArray.prototype.subarray()
, .getReader()
, .read()
, чтобы получить N
фрагменты ArrayBuffer
как TypedArray
at pull
от Uint8Array
. Когда обработаны N
фрагменты, равные .byteLength
of ArrayBuffer
, передайте массив конструктора Uint8Array
в Blob
для рекомбинации частей файла в один файл в браузере; затем отправьте Blob
на сервер.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input id="file" type="file">
<br>
<progress value="0"></progress>
<br>
<output for="file"><img alt="preview"></output>
<script type="text/javascript">
const [input, output, img, progress, fr, handleError, CHUNK] = [
document.querySelector("input[type='file']")
, document.querySelector("output[for='file']")
, document.querySelector("output img")
, document.querySelector("progress")
, new FileReader
, (err) => console.log(err)
, 1024 * 1024
];
progress.addEventListener("progress", e => {
progress.value = e.detail.value;
e.detail.promise();
});
let [chunks, NEXT, CURR, url, blob] = [Array(), 0, 0];
input.onchange = () => {
NEXT = CURR = progress.value = progress.max = chunks.length = 0;
if (url) {
URL.revokeObjectURL(url);
if (blob.hasOwnProperty("close")) {
blob.close();
}
}
if (input.files.length) {
console.log(input.files[0]);
progress.max = input.files[0].size;
progress.step = progress.max / CHUNK;
fr.readAsArrayBuffer(input.files[0]);
}
}
fr.onload = () => {
const VIEW = new Uint8Array(fr.result);
const LEN = VIEW.byteLength;
const {type, name:filename} = input.files[0];
const stream = new ReadableStream({
pull(controller) {
if (NEXT < LEN) {
controller
.enqueue(VIEW.subarray(NEXT, !NEXT ? CHUNK : CHUNK + NEXT));
NEXT += CHUNK;
} else {
controller.close();
}
},
cancel(reason) {
console.log(reason);
throw new Error(reason);
}
});
const [reader, processData] = [
stream.getReader()
, ({value, done}) => {
if (done) {
return reader.closed.then(() => chunks);
}
chunks.push(value);
return new Promise(resolve => {
progress.dispatchEvent(
new CustomEvent("progress", {
detail:{
value:CURR += value.byteLength,
promise:resolve
}
})
);
})
.then(() => reader.read().then(data => processData(data)))
.catch(e => reader.cancel(e))
}
];
reader.read()
.then(data => processData(data))
.then(data => {
blob = new Blob(data, {type});
console.log("complete", data, blob);
if (/image/.test(type)) {
url = URL.createObjectURL(blob);
img.onload = () => {
img.title = filename;
input.value = "";
}
img.src = url;
} else {
input.value = "";
}
})
.catch(e => handleError(e))
}
</script>
</body>
</html>
plnkr http://plnkr.co/edit/AEZ7iQce4QaJOKut71jk?p=preview
Вы также можете использовать утилиту fetch()
fetch(new Request("/path/to/server/", {method:"PUT", body:blob}))
Кому передать тело для requestзапросите, запустите эти шаги:
- Пусть тело будет запросом body.
Если тело равно null, тогда очередь запроса на выборку по запросу обрабатывать запрос конца тела для запроса и прервать эти шаги.
Пусть чтение будет результатом чтения фрагмента из потока bodys.
Когда чтение выполняется с объектом, чье свойство
done
является ложным и чье свойствоvalue
является объектомUint8Array
, запустите эти субшаги:
- Пусть байты представляют собой последовательность байтов, представленную объектом
Uint8Array
.Передача байтов.
Увеличить количество переданных байтов байтов по длине байтов.
Повторите вышеуказанный шаг.
Когда чтение выполняется с объектом, чье свойство
done
равно true, очередь на задание выборки по запросу для обработки запроса на конец тела для запроса.Когда чтение выполняется со значением, которое не соответствует ни одному из вышеприведенных шаблонов, или чтение отклонено, прекратите текущую fetch с фатальной причиной.
См. также