web-dev-qa-db-fra.com

Promesses Javascript avec FileReader ()

J'ai le code HTML suivant:

<input type='file' multiple>

Et voici mon code 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]);
}

Donc ma question est, comment puis-je rendre cette boucle synchrone? C’est d’abord attendre que le fichier se termine, puis passer au fichier suivant. Quelqu'un m'a dit d'utiliser JS Promises. Mais je ne peux pas le faire fonctionner. Voici ce que j'essaye:

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
            }
        });
    };
}

Merci d'avance...

11
Zahid Saeed

Si vous voulez le faire de manière séquentielle (pas de manière synchrone) en utilisant Promises, vous pouvez faire quelque chose comme: 

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);
  });
}
18
mido

Nous avons modifié midos answer pour que cela fonctionne comme suit:

function readFile(file){
  return new Promise((resolve, reject) => {
    var fr = new FileReader();  
    fr.onload = () => {
      resolve(fr.result )
    };
    fr.readAsText(file.blob);
  });
}
6
Jens Lincke

La nature de FileReader est que vous ne pouvez pas rendre son fonctionnement synchrone.

Je soupçonne que vous n'avez pas vraiment besoin ou que vous ne voulez pas que ce soit synchrone, mais simplement que vous souhaitiez obtenir correctement les URL résultantes. Si tel était le cas, je ne pense pas que les promesses aideraient vraiment. Au lieu de cela, gardez simplement une trace du nombre d'opérations en suspens afin de savoir quand vous avez terminé:

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;
    });
}

Ou si vous voulez, pour une raison quelconque, lire les fichiers séquentiellement (mais toujours de manière asynchrone), vous pouvez le faire en planifiant le prochain appel uniquement lorsque le précédent est terminé:

// 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);
    }
}
5
T.J. Crowder

Voici une autre modification apportée à la réponse de Jens (reprise de la réponse de Mido) pour vérifier en plus la taille du fichier:

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.onloadend = () => {
                data = fr.result;
                resolve(data)
            };
            fr.readAsDataURL(file);
            }
        });
    }
0
JasonZiolo