Javascript范围问题,对象在分配后没有数据

时间:2013-11-30 19:18:10

标签: javascript node.js express

我正在使用NodeJS创建一个快速端点,它将从存储在我服务器上的图像中检索元数据。我有以下端点逻辑代码:

/*
 * Gallery Controller
 */

var fs = require('fs'),
    _ = require('underscore'),
    im = require('imagemagick');

/**
 * List paths to images stored in selected gallery
 */
exports.list = function(req, res) {
    var dir = 'public/images/' + req.params.id;

    fs.readdir(dir, function(err, files) {
        if (err) return res.send({error: 'No gallery found with provided id'}, 404);

        if (files.length > 0) {
            var collection = [],
                myData = {};

            files.forEach(function(file) {
                if(file === '.DS_Store') return;

                im.readMetadata( dir + '/' + file, function(err, metadata) {
                    if (err) throw err;
                    myData = metadata;
                    console.log(myData); // logs as object with expected data
                });

                console.log(myData); // logs as empty {}
                collection.push(myData);
            });

            console.log(collection); // logs as [ {}, {} ]

            res.json(collection, 200);
        } else {
            res.json({error: 'Selected gallery is empty'}, 404);
        }
    });
};

我列出了日志在终端中出现的内容,为什么我会遇到这个范围问题?我似乎无法绕过它。如果我尝试return metadata obj并将其分配给var,我会收到以下错误:TypeError: Converting circular structure to JSON

2 个答案:

答案 0 :(得分:1)

使用async模块,它可以通过多种方式改善您的生活。

您遇到的问题是我看到的常见问题,而且您的循环是异步的,但您将其视为串行内容。

而不是执行files.forEach,而是想要异步循环它们,然后在循环完成时再做一些事情。您可以使用async.each

async.each(files, function (file, next) {
  if (file === '.DS_Store') return next();

  im.readMetadata(path.join(dir, file), function (e, data) {
    collection.push(data);
    next(err);
  });
}, function (err) {
  if (err) throw err;

  console.log(collection);
});

作为替代方案,更合适的解决方案可能是使用async.map

async.map(files, function (file, next) {
  if (file === '.DS_Store') return next();

  im.readMetadata(path.join(dir, file), next);
}, function (err, collection) {
  if (err) throw err;

  console.log(collection);
});

答案 1 :(得分:1)

您需要重新构建代码:

files.forEach(function(file, i) {
  if (file === '.DS_Store') return; // see text

  im.readMetadata( dir + '/' + file, function(err, metadata) { 
    if (err) throw err;
    collection.push(metadata);
    if (i === files.length - 1) {
      res.json(collection); // see text
    }
  });
});

原因是元数据仅在调用readMetadata的回调函数时可用;这就是异步I / O在Node中的工作方式。

在该回调中,您将元数据添加到集合中。如果forEach的迭代已经到达最后一个元素(i是当前元素的索引,当它的值小于数组的大小时,它是最后一个元素),响应发送。

两个问题:

  • 如果.DS_Store是目录中的最后一个/唯一文件,则此代码将失败,因为它永远不会发回响应;我会留给你处理那个案子;)
  • 默认情况下,
  • res.json会返回200状态,因此您无需指定;如果您想要指定状态,则需要res.json(200, collection)(参数交换)