1
votes

Synchroniser un forEach avec un événement à l'intérieur (Firebase & node 6.11.5)

Je lis, bit par bit, tous les fichiers PNG dans un répertoire et je dois résumer certaines données dans un json. Le problème est que, si je comprends bien, le lecteur PNG envoie un événement asynchrone "analysé" une fois terminé. Cela cause que la fonction se termine avant que json soit rempli ...

J'utilise le nœud 6.11.5, donc je ne peux pas utiliser sync / await.

var fs = require('fs'),
PNG = require('pngjs').PNG;

exports.movie = functions.https.onRequest((req, res) => {
    console.log('********** START FUNCTION ************');

    var movieFolder = 1;
    if (req.query.id) movieFolder = '../movies/' + req.query.id + '/png/';

    var exitJson = [];

    fs.readdir(movieFolder, (err, files) => {
        files.forEach((file) => {
          fs.createReadStream(movieFolder + file)
            .pipe(new PNG({
                filterType: 1
             }))
            .on('parsed', function () {
                console.log('Parsing: ' + movieFolder + file);
                exitJson.push({
                    width: this.width,
                    height: this.height,
                    data: []
                });
            });
        });
    });
    console.log('************* FINISHED *************');
    res.status(200).json(exitJson);
});


3 Réponses :


1
votes

Vous pouvez utiliser un simple compteur élément traité pour détecter si tous vos rappels ont été résolus.

var movieFolder = 1;
if (req.query.id) movieFolder = '../movies/' + req.query.id + '/png/';

var exitJson = [];

var itemsProcessed = 0;

fs.readdir(movieFolder, (err, files) => {
    files.forEach((file) => {
      fs.createReadStream(movieFolder + file)
        .pipe(new PNG({
            filterType: 1
         }))
        .on('parsed', function () {
            console.log('Parsing: ' + movieFolder + file);
            exitJson.push({
                width: this.width,
                height: this.height,
                data: []
            });
            itemsProcessed++;
            if (itemsProcessed === files.length) {
              console.log('************* FINISHED *************');
              res.status(200).json(exitJson);
            }
        });
    });
});


0 commentaires

0
votes

<pre><code id="res"></code></pre>
const exports={};const sizes={'foo.png':[100,200],'bar.png':[200,200],'baz.png':[300,200]};Promise.delay = (t) => new Promise(r => setTimeout(r, t)); const randomTime = (a = 500, b = 1500) => Math.floor(Math.random() * b) + a;
const require=src=>({'fs':{readdir:(d,c)=>{Promise.delay(randomTime()).then(() => c(null,['foo.png','bar.png','baz.png']))},createReadStream:(path)=>({pipe:(f)=>({on:(e,c)=>{const s=sizes[path.split('/').slice(-1)[0]];const a={width:s[0],height:s[1]};a.c=c;Promise.delay(randomTime()).then(() => a.c())}})})},'pngjs':{PNG:class PNG{constructor(a){}}},'firebase-functions':{https:{onRequest:(handler)=>{handler({query:({id:2})},{status:(s)=>({json:(a) => document.getElementById('res').innerHTML = `<pre><code>${JSON.stringify(a, null, 4)}</code></pre>`})})}}}})[src];

// ------------------- ignore the above

const fs = require('fs');
const PNG = require('pngjs').PNG;
const functions = require('firebase-functions');

/**
 * Using a new Promise, we can perform multiple async tasks all contained 
 * within that one Promise which can be resolved or rejected. We read the 
 * folder directory for its files and pass it on to our Promised 'readFiles'.
 */
function readMovieFiles(folder) { console.log('readMovieFiles', folder)
  return new Promise((res, rej) => {
    fs.readdir(folder, (err, files) => {
      readFiles(files, folder).then(res).catch(rej)
    });
  });
}

/**
 * Given an array of file names within a folder, we can chain together the 
 * file promises using the reduce method. Starting at an initial value of
 * Promise<[]>, each file in the array will be read sequentially.
 */
function readFiles(files, folder) { console.log('readFiles', folder, files)
  return Promise.all(files.map(name => readFile(folder + name)));
}

/**
 * We read a file and in the parsed callback, we call the res() and pass it 
 * the newly constructed array containing the newest file to be parsed.
 */
function readFile(path) { console.log('readFile', path)
  return new Promise((res, rej) => {
    fs.createReadStream(path)
      .pipe(new PNG({ filterType: 1 }))
      .on('parsed', function() {
        console.log('parsedFile', path)
        res({
          data: [],
          width: this.width,
          height: this.height
        });
      });
  });
}

exports.movie = functions.https.onRequest((req, res) => {

  console.log('********** START FUNCTION ************');
  
  if (!req.query.id) req.query.id = 1;
    
  readMovieFiles(`../movies/${req.query.id}/png/`).then(exitJson => {
    res.status(200).json(exitJson);
  }).catch(error => {
    res.status(500).json(error);
  });
  
  console.log('************* FINISHED *************');
});


3 commentaires

Merci! Pouvez-vous expliquer ce que vous avez fait? Vous mettez toutes les fonctions dans une promesse, non? C'était mon idée ... mais trop complexe pour moi ...


Bien sûr, j'ai ajouté quelques commentaires. La fonction readFiles est un peu déroutante, mais elle convertit essentiellement un tableau de noms de fichiers en un tableau de promesses et les exécute séquentiellement. Par exemple, si vous voulez qu'ils s'exécutent en même temps, vous pouvez simplement dire Promise.all (files.map (name => readFile (dossier + nom, arr))) .


Cool! Merci beaucoup!



0
votes

Vous pouvez charger les fichiers un par un via des appels récursifs. N'oubliez pas de vérifier les erreurs.

exports.movie = functions.https.onRequest((req, res) => {
    var movieFolder = 1;
    if (req.query.id) 
        movieFolder = '../movies/' + req.query.id + '/png/';

    var exitJson = [];
    fs.readdir(movieFolder, function (err, files) {
        var sendError = (err) => res.status(500).send(err.message);

        if (err)
            return sendError(err);

        function loadFile (i) {
            if (i == files.length) 
                return res.status(200).json(exitJson); // !!!DONE!!!

            var file = files[i];
            fs.createReadStream(movieFolder + file)
                .pipe(new PNG({filterType: 1}))
                .on('parsed', function () {
                    console.log('Parsing: ' + movieFolder + file);
                    exitJson.push({width: this.width, height: this.height, data: []});
                    loadFile (i + 1); // go to next file
                })
                .on('error', sendError);
        }

        loadFile(0); // start recursion
    }); 
});


0 commentaires