NodeJS对象返回未定义的

时间:2016-04-29 21:24:06

标签: node.js mongodb express

此路由器用于访问特定列表。在示例中,comments是一个对象数组。当我在gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});对象中插入此comments时,它已成功完成。但在前端(我使用玉)。我尝试使用此list.comments[i].gvUrl,它返回undefined。它甚至会在for循环中返回undefined!我做错了什么?

router.get('/:id', function (req, res, next) {
    List.findOne({listurl: req.params.id}, function (err, doc) {
        var z = 0;
        if (!err && doc != null) {
            for (var i = 0; i < doc.comments.length; i++) {
                User.findOne({Name: doc.comments[i].commenter}, function (err, data) {
                    if (data) {
                        doc.comments[z++].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
                    } else {
                        doc.comments[z++].gvUrl = 'noGravs';
                    }
                });
            }

           //this line returns unfefined;
               console.log(doc.comments[0].gvUrl);

                res.render('list', {
                    appTitle: doc.Title,
                    list: doc
                 });
        }
        else {
            res.status(404).render('404', {appTitle: "Book not found"});
        }
    });
});

1 个答案:

答案 0 :(得分:1)

您正在for循环内启动一堆异步操作,并期望在for循环完成后完成所有异步操作。但是,实际上,它们都没有完成,因此您的doc.comments数组尚未填充。您在填充之前尝试使用它,这就是为什么您在尝试使用它时将其视为已定义的原因。

解决此问题的最佳方法是学习如何使用Promise,然后使用Blubird&#39; Promise.map()或ES6 Promise.all()等多个请求触发,然后让promise引擎告诉您何时所有请求都已完成。

如果没有将数据库调用转换为使用Promises,您可以手动编码以了解所有内容何时完成,如下所示:

手动编码回调实施

router.get('/:id', function (req, res, next) {
    List.findOne({listurl: req.params.id}, function (err, doc) {
        var doneCnt = 0;
        if (!err && doc != null) {
            for (var i = 0; i < doc.comments.length; i++) {
                (function(index) {
                    User.findOne({Name: doc.comments[i].commenter}, function (err, data) {
                        ++doneCnt;
                        if (err) {
                            // need some form of error handling here
                            doc.comments[index].gvUrl = "";
                        } else {
                            if (data) {
                                doc.comments[index].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
                            } else {
                                doc.comments[index].gvUrl = 'noGravs';
                            }
                        }
                        // if all requests are done now
                        if (doneCnt === doc.documents.length) {
                            res.render('list', {
                                appTitle: doc.Title,
                                list: doc
                             });
                        }
                    });
                })(i);
            }
        }
        else {
            res.status(404).render('404', {appTitle: "Book not found"});
        }
    });
}

此代码可识别有关异步操作的以下内容:

  1. 您正在循环中触发多个User.findOne()异步操作。
  2. 这些异步操作可以按任何顺序完成。
  3. 循环完成后,这些异步操作将无法完成。所有循环都完成了启动操作。他们将在以后完成一些不确定的时间。
  4. 要知道所有异步操作何时完成,它会保留一个计数器来计算已完成的数量,并在计数达到启动的总请求数时呈现页面。这是&#34;手册&#34;如何知道所有这些都已完成的方式。
  5. 蓝鸟承诺实施

    以下是如何使用Bluebird Promises库并将数据库操作转换为支持Promises的方法:

    var Promise = require('bluebird');
    // promisify the methods of the List and User objects
    var List = Promise.promisifyAll(List);
    var User = Promise.promisifyAll(User);
    
    router.get('/:id', function (req, res, next) {
        List.findOneAsync({listurl: req.params.id}).then(function(doc) {
            if (!doc) {
                throw "Empty Document";
            }
            return Promise.map(doc.comments, function(item, index, length) {
                return User.findOneAsync({Name: item.commenter}).then(function(data) {
                    if (data) {
                        item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
                    } else {
                        item.gvUrl = 'noGravs';
                    }
                });
            }).then(function() {
                return doc;
            });
        }).then(function(doc) {
            res.render('list', {
                appTitle: doc.Title,
                list: doc
            });
        }).catch(function(err) {
            res.status(404).render('404', {appTitle: "Book not found"});
        });
    }
    

    以下是如何运作的:

    1. 加载Bluebird promise库
    2. UserList对象添加有效方法,以便为每个返回承诺的方法添加新版本。
    3. 致电List.findOneAsync()。方法名称上的"Async"后缀表示.promisifyAll()添加的新方法。
    4. 如果没有doc,那么将拒绝承诺的投掷将在最后的.catch()处理。 Promise是异步throw-safe,非常方便异步错误处理。
    5. 致电Promise.map()上的doc.comments。这将自动迭代doc.comments数组并在数组中的每个项上调用迭代器(类似于Array.prototype.map(),除了此处它收集迭代器返回的所有promise并返回一个新的promise,当所有解决了底层的promises。通过这种方式,它允许所有迭代器并行运行,并告诉你何时完成所有迭代器。
    6. 迭代器调用User.findOneAsync()并使用结果设置doc.comments[index].gvUrl值。
    7. .then()上有一个额外的Promise.map()处理程序,只是为了将该承诺的已解析值更改为doc对象,以便我们可以从外部获取承诺处理程序。
    8. 从外部承诺获得成功,渲染。
    9. 对于来自外部承诺的错误,请显示404页面。请记住,在整个计划中任何地方的任何被拒绝的承诺都会传播,并在顶级被拒绝。这种在promises中异步错误的自动传播非常有用。
    10. ES6承诺实施

      这可以通过没有Bluebird承诺库的直接ES6承诺来完成,但是您必须手动完成更多的事情:

      1. 您必须宣传List.findOne()操作。
      2. 您必须宣传User.findOne()操作。
      3. 您必须使用常规doc.comments.map()次迭代并将每个单独的保证收集到一个数组中,然后在该数组上使用Promise.all()而不是让Promise.map()执行所有这些操作为你。
      4. 以下是代码:

        // manually promisify findOne
        List.findOneAsync = function(queryObj) {
            return new Promise(function(resolve, reject) {
                List.findOne(queryObj, function(err, data) {
                    if (err) return reject(err);
                    resolve(data);
                });
            }
        }
        User.findOneAsync = function(queryObj) {
            return new Promise(function(resolve, reject) {
                User.findOne(queryObj, function(err, data) {
                    if (err) return reject(err);
                    resolve(data);
                });
            }
        }
        
        router.get('/:id', function (req, res, next) {
            List.findOneAsync({listurl: req.params.id}).then(function(doc) {
                if (!doc) {
                    throw "Empty Document";
                }
                var promises = doc.comments.map(function(item, index) {
                    return User.findOneAsync({Name: item.commenter}).then(function(data) {
                        if (data) {
                            item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
                        } else {
                            item.gvUrl = 'noGravs';
                        }
                    });
                });
                return Promise.all(promises).then(function() {
                    return doc;
                });
            }).then(function(doc) {
                res.render('list', {
                    appTitle: doc.Title,
                    list: doc
                });
            }).catch(function(err) {
                res.status(404).render('404', {appTitle: "Book not found"});
            });
        }