Javascript Promises с FileReader()
У меня есть следующий HTML-код:
<input type='file' multiple>
И вот мой код JS:
var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
var fr = new FileReader();
for(var i = 0; i < inputFiles.files.length; i++){
fr.onload = function(){
console.log(i) // Prints "0, 3, 2, 1" in case of 4 chosen files
}
}
fr.readAsDataURL(inputFiles.files[i]);
}
Итак, мой вопрос, как я могу сделать этот цикл синхронным? То есть сначала дождитесь окончания загрузки файла, затем перейдите к следующему файлу. Кто-то сказал мне использовать JS Promises. Но я не могу заставить его работать. Вот что я пытаюсь:
var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
for(var i = 0; i < inputFiles.files.length; i++){
var fr = new FileReader();
var test = new Promise(function(resolve, reject){
console.log(i) // Prints 0, 1, 2, 3 just as expected
resolve(fr.readAsDataURL(inputFiles.files[i]));
});
test.then(function(){
fr.onload = function(){
console.log(i); // Prints only 3
}
});
};
}
Заранее спасибо...
Ответы
Ответ 1
Если вы хотите сделать это последовательно (не синхронно) с помощью Promises, вы можете сделать что-то вроде:
var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
var promise = Promise.resolve();
inputFiles.files.map( file => promise.then(()=> pFileReader(file)));
promise.then(() => console.log('all done...'));
}
function pFileReader(file){
return new Promise((resolve, reject) => {
var fr = new FileReader();
fr.onload = resolve; // CHANGE to whatever function you want which would eventually call resolve
fr.readAsDataURL(file);
});
}
Ответ 2
Мы изменили ответ midos, чтобы он работал следующим образом:
function readFile(file){
return new Promise((resolve, reject) => {
var fr = new FileReader();
fr.onload = () => {
resolve(fr.result )
};
fr.readAsText(file.blob);
});
}
Ответ 3
Характер FileReader
заключается в том, что вы не можете сделать его работу синхронной.
Я подозреваю, что вам действительно не нужно или хотите, чтобы он был синхронным, просто чтобы вы правильно получили результирующие URL-адреса. Если это так, я бы не подумал, что обещания действительно помогут. Вместо этого просто отслеживайте, сколько выдающихся операций у вас есть, чтобы вы знали, когда закончите:
var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
var data = []; // The results
var pending = 0; // How many outstanding operations we have
// Schedule reading all the files (this finishes before the first onload
// callback is allowed to be executed)
Array.prototype.forEach.call(inputFiles.files, function(file, index) {
// Read this file, remember it in 'data' using the same index
// as the file entry
var fr = new FileReader();
fr.onload = function() {
data[index] = fr.result;
--pending;
if (pending == 0) {
// All requests are complete, you're done
}
}
fr.readAsDataURL(file);
++pending;
});
}
Или если вы хотите по какой-то причине последовательно читать (но все же асинхронно) файлы, вы можете сделать это, запланировав следующий вызов только тогда, когда предыдущий будет завершен:
// Note: This assumes there is at least one file, if that
// assumption isn't valid, you'll need to add an up-front check
var inputFiles = document.getElementsByTagName("input")[0];
inputFiles.onchange = function(){
var index = 0;
readNext();
function readNext() {
var file = inputFiles.files[index++];
var fr = new FileReader();
fr.onload = function() {
// use fr.result here
if (index < inputFiles.files.length) {
// More to do, start loading the next one
readNext();
}
}
fr.readAsDataURL(file);
}
}
Ответ 4
Я обновляю ответ дженса Линке, добавляя рабочий пример и вводя синтаксис async/await
function readFile(file) {
return new Promise((resolve, reject) => {
let fr = new FileReader();
fr.onload = x=> resolve(fr.result);
fr.readAsDataURL(file) // or readAsText(file) to get raw content
})}
function readFile(file) {
return new Promise((resolve, reject) => {
let fr = new FileReader();
fr.onload = x=> resolve(fr.result);
fr.readAsDataURL(file) // or readAsText(file) to get raw content
})}
async function load(e) {
for(let [i,f] of [...e.target.files].entries() ){
msg.innerHTML += '<h1>File ${i}: ${f.name}</h1>';
let p = document.createElement("pre");
p.innerText += await readFile(f);
msg.appendChild(p);
}
}
<input type="file" onchange="load(event)" multiple />
<div id="msg"></div>
Ответ 5
Вот еще одна модификация ответа Йенса (совмещенная с ответом Mido) для дополнительной проверки размера файла:
function readFileBase64(file, max_size){
max_size_bytes = max_size * 1048576;
return new Promise((resolve, reject) => {
if (file.size > max_size_bytes) {
console.log("file is too big at " + (file.size / 1048576) + "MB");
reject("file exceeds max size of " + max_size + "MB");
}
else {
var fr = new FileReader();
fr.onload = () => {
data = fr.result;
resolve(data)
};
fr.readAsDataURL(file);
}
});
}