Синхронизируйте forEach с событием внутри (Firebase & node 6.11.5)

1

Я читаю, бит за битом, все файлы PNG в каталоге, и мне нужно обобщить некоторые данные в формате json. Проблема состоит в том, что, если я понимаю, программа чтения PNG отправляет асинхронное событие, "проанализированное", когда закончено. Это потому, что функция выходит из-за заселения JSON...

Я использую узел 6.11.5, поэтому я не могу использовать 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);
});
Теги:
firebase
google-cloud-functions

3 ответа

1
Лучший ответ

Вы можете использовать простой счетчик itemsProcessed чтобы определить, были ли разрешены все ваши обратные вызовы.

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
    Спасибо! Не так элегантно, может быть ... но это работает!
0

Вы можете загружать файлы один за другим через рекурсивные вызовы. Не забудьте проверить ошибки.

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

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 *************');
});
<pre><code id="res"></code></pre>
  • 0
    Спасибо! Можете ли вы объяснить, что вы сделали? Вы помещаете все функции в обещание, верно? Была ли моя идея ... но слишком сложной для меня ...
  • 0
    Конечно, я добавил несколько комментариев. Функция readFiles немного сбивает с толку, но, по сути, она конвертирует массив имен файлов в массив обещаний и выполняет их последовательно. Например, если вы хотите, чтобы они запускались одновременно, вы можете просто сказать Promise.all(files.map(name => readFile(folder + name, arr))) .
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню