Run a loop over a callback, node js

218 views Asked by At

I need to resize some images. Problem is the library I'm using do one resize per callback.

I want to resize several images so I put it in a loop:

exports.resizeImages = function (req, res) {
    var images = fs.readdirSync('uploads/');

    for (var n = 0; n < files.length; n++) {
        var tgt = 'uploads/resized/' + images[n];

        gm(tgt).resize(150).write(tgt, function (err) {
            if (err) {
                console.log('resizing failed');
                res.status(400).send('failed to resize');
                return;
            }
            if (n == images.length) {

                res.status(200).send();
            }
        });
    }
}

I'm aware I can't do it like this. I need make the loop wait until the callback responds somehow. I've seen some examples but I can't get it to work.

Any ideas?

4

There are 4 answers

0
Alex On BEST ANSWER

You could also use the node async module

Something like this:

var async = require('async');

exports.resizeImages = function (req, res) {
    var images = fs.readdirSync('uploads/');

    async.each(images, function(file, callback) {
        var tgt = 'uploads/resized/' + file;
        gm(tgt).resize(150).write(tgt, callback);
    }, function(err) {
        if(err) {
            console.log('resizing failed');
            return res.status(400).send('failed to resize');
        } else {
            //no error
            return res.status(200).send();
        }
    });
}
0
djechlin On

You need to write a for loop using promises. Pick your favorite promise library. The details are covered in this question or this question. Or in this blog post:

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
var resolver = Promise.defer();

var loop = function() {
    if (!condition()) return resolver.resolve();
    return Promise.cast(action())
        .then(loop)
        .catch(resolver.reject);
};

process.nextTick(loop);

return resolver.promise;

};

0
lemonzi On

Promises are great for this, indeed. There are other libraries that wrap callbacks into this kind of abstractions, but since Promises are standard it's better to learn just one thing.

If you want to keep it bare, you can use an external counter:

var images = fs.readdirSync('uploads/');
var processed = 0;

for (var n = 0; n < files.length; n++) {
    var tgt = 'uploads/resized/' + images[n];

    gm(tgt).resize(150).write(tgt, function (err) {
        if (err) {
            console.log('resizing failed');
            res.status(400).send('failed to resize');
            return;
        }
        processed++;
        if (processed == images.length) {
            res.status(200).send();
        }
    });
}

That is assuming that you only send 200 OK if all images have been correctly resized.

0
officert On

Like others have mentioned you could use promises, but if you don't want to start using promises in your project async.js is perfect for this kind of thing.

Your code rewritten using async.js:

exports.resizeImages = function (req, res) {

    async.waterfall([
        function readImages_step(done) {
            readdir('uploads/', done);
        },
        function uploadImages_step(images, done) {
            async.each(images, function(image, cb) {
                var target = 'uploads/resized/' + image;

                gm(target).resize(150).write(target, cb);
            }, done);
        }
    ], function (err) {
        if (err) {
            console.log('resizing failed');
            return res.status(400).send('failed to resize');
        }
        return res.status(200).send();
    }
};

I changed your readdirsync call to be asynchronous. Async.each will run each upload in parallel.