为什么变量赋值不能按预期工作?

时间:2014-12-09 11:16:36

标签: javascript node.js

我们有一系列人物和一系列地址。在每个人中,都有一个地址的id。我们尝试做一个像#join一样的'在JavaScript中,并且无法在返回对象中添加新字段。

var ret;
app.get('/tp/show/method', function (req, res) {
    ret={};
    User2Model.find(function(err, users) {
        if(err){
            console.error('Error find: '+err);
        }
        var last_id;
        for(var user_id in users){
            last_id=users[user_id]._id;
            ret[last_id]=users[user_id];
        }
        for(var user_id in users){
            AdrModel.find({ 'user_id': users[user_id]['id_adr'] },function(err, adr) {
                if (err){
                    console.error('Error find: '+err);
                }
                for(var i in adr){
                    for(var user_id in users){
                        if (users[user_id].id_adr==adr[i].user_id) {

                            /* ok id found, so add all adresses to the result: */
                            /* The following line doesn't work: */
                            ret[users[user_id]._id]=adr;

                            if(users[user_id]._id==last_id){
                                var url_parts = url.parse(req.url, true);
                                var query = url_parts.query;
                                res.setHeader(
                                    'content-type', 'application/javascript'
                                );
                                json=query.callback+'('+JSON.stringify({
                                    data:{success: 1, value: ret }
                                })+')';
                                res.send(json);
                            }
                            break;
                        }
                    }
                }
            });
        }
    });
});

变量ret是全局的,所以我们应该能够修改它,但是当我们覆盖那里已经存在的一些属性时,返回结果就会接受。如果我们尝试添加像" addr"这样的新属性,它就无法工作。我错过了什么?

3 个答案:

答案 0 :(得分:3)

这是尝试使用同步方法处理异步代码而导致的典型问题。您的整个尝试都是不可修复的,您需要废弃它。

一种广泛采用的处理异步代码而不会疯狂的方法是使用promises。

如果您使用了promise库,那么这就是您的代码的样子。为了示例,我在这里使用Bluebird

var findAddress = Promise.promisify(AdrModel.find);
var findUsers = Promise.promisify(User2Model.find);

// a helper function that accepts a user object and resolves the address ID
function attachAddrToUser(user) {
    return findAddress({
        user_id: user.id_adr
    }).then(function (address) {
        user.address = address;
        return user;
    }).catch(function (e) {
        console.error("error finding address for user ID " + user.id_user, e);
    });
}

findUsers().then(function (users) {
    var pending = [], id_user;
    for (id_user in users) {
        pending.push(attachAddrToUser(users[id_user]));
    }
    Promise.all(pending).then(function (users) {
        // all done, build and send response JSON here
    }).catch(function (e) {
        // don't forget to add error handling
    });
});

在这里工作jsFiddle:http://jsfiddle.net/Tomalak/2hdru6ma/

注意:attachAddrToUser()修改传递给它的用户对象。这不是完全干净,但它在这种情况下是有效的。

答案 1 :(得分:2)

正如我在上述@ Tomalak解决方案的评论中指出的,事件也可用于管理异步环境中的程序流。

以下是使用该方法的备用部分解决方案。

  

请注意实现这一目标的各种方法(我知道至少有三个或四个,如果你接受通过使用外面定义的回调可以改善" Callback Hell"的痛苦我们更倾向于使用事件,因为它们是一种更自然的方式让我思考这类问题。

外卖

  1. 事件是在异步编程环境中管理程序流的一种高效且易于理解的方式。
  2. 可以使用事件来传输任何数据,而不是简单的触发器,因此可以将它们用于任何目的。
  3. 事件可以轻松调用其他事件而无需担心范围。
  4. 事件处理允许您展开代码,以便更容易跟踪和调试,以及减少深层嵌套或递归代码中常见的堆栈负担。换句话说,事件很快且内存效率很高。
  5. 解释

    代码首先定义了两个模拟:

    1. 提供get方法的App类,允许我们模拟OP的app实例,
    2. User2Model单例,为同一目的提供find函数。
    3. 然后记录以下事件:

      1. error - 在任何错误上调用以将消息打印到控制台并退出程序
      2. get - 使用app.get方法的结果触发,并使用{req:req,res:res}立即触发processUsers事件
      3. processUsers - 由get事件处理程序使用模拟的用户对象数组触发,设置结果对象和last_id值,然后调用nextUser事件。
      4. nextUser - 由processUsers事件触发,该事件从users数组中选择下一个用户,设置evt.last_id,将用户添加到evt.results,并自行发出,或者如果有没有用户留在evt.users数组上,发出complete
      5. complete - 由nextUser触发,只是将消息输出到控制台。
      6. 接下来使用' + eventName约定来定义事件处理程序。

        最后,我们

        1. 定义一个eventHandlers对象,以将处理程序映射到适当的事件,
        2. 实例化我们的app实例和
        3. 使用回调调用其get方法,该回调只会发出get事件以启动滚动。
        4. 我使用jsdoc记录了大部分解决方案并添加了日志消息,以便在发出每个事件并调用其处理程序时显示进度。运行的结果包含在代码之后。 (为了简洁起见,http req和res对象已经从日志消息中注释掉了。)

          最后一点,虽然这个例子长269行,但大多数都是文档。

          实际代码(没有模拟)只有大约20或25行。

          代码

          /*
          
           Example of using events to orchestrate program flow in an async
           environment.
          
           */
          
          var util = require('util'),
              EventEmitter = require('events').EventEmitter;
          
          // mocks
          
          /**
           * Class for app object (MOCK)
           * @constructor
           * @augments EventEmitter
           */
          var App = function (handlers) {
            EventEmitter.call(this);
            this.init(handlers);
          };
          util.inherits(App, EventEmitter);
          
          /**
           * Inits instance by setting event handlers
           *
           * @param {object} handlers
           * @returns {App}
           */
          App.prototype.init = function (handlers) {
            var self = this;
            // set event handlers
            Object.keys(handlers).forEach(function (name) {
              self.on(name, handlers[name]);
            });
            return self;
          };
          
          /**
           * Invokes callback with req and res
           * @param uri
           * @param {App~getCallback} cb
           */
          App.prototype.get = function (uri, cb) {
          
            console.log('in app.get');
          
            var req = {uri: uri},
                res = {uri: uri};
            /**
             * @callback App~getCallback
             * @param {object} req - http request
             * @param {object} res - http response
             * @fires {App#event:get}
             */
            cb(req, res);
          };
          
          /**
           * Data access adapter - (MOCK)
           * @type {object}
           */
          var User2Model = {};
          /**
           *
           * @param {User2Model~findCallback} cb
           */
          User2Model.find = function (cb) {
            var err = null,
                users = [
                  {_id: 1},
                  {_id: 2}
                ];
            /**
             * @callback User2Model~findCallback
             * @param {Error} err
             * @param {Array} users
             */
            cb(err, users);
          };
          
          
          // events
          
          /**
           * Error event.
           *
           * @event App#error
           * @type {object}
           * @property {object} [req] - http request
           * @property {object} [res] - http response
           * @property {string} where - name of the function in which the error occurred
           * @property {Error} err - the error object
           */
          
          /**
           * Get event - called with the result of app.get
           *
           * @event App#get
           * @type {object}
           * @property {object} req - http request
           * @property {object} res - http response
           */
          
          /**
           * ProcessUsers event - called
           *
           * @event App#processUsers
           * @type {object}
           * @property {object} req - http request
           * @property {object} res - http response
           * @property {Array} users - users
           */
          
          /**
           * NextUser event.
           *
           * @event App#nextUser
           * @type {object}
           * @property {object} req - http request
           * @property {object} res - http response
           * @property {Array} users
           * @property {*} last_id
           * @property {object} result
           */
          
          /**
           * Complete event.
           *
           * @event App#complete
           * @type {object}
           * @property {object} req - http request
           * @property {object} res - http response
           * @property {Array} users
           * @property {*} last_id
           * @property {object} result
           */
          
          // event handlers
          
          /**
           * Generic error handler
           *
           * @param {App#event:error} evt
           *
           * @listens App#error
           */
          var onError = function (evt) {
            console.error('program error in %s: %s', evt.where, evt.err);
            process.exit(-1);
          };
          
          /**
           * Event handler called with result of app.get
           *
           * @param {App#event:get} evt - the event object
           *
           * @listens App#appGet
           * @fires App#error
           * @fires App#processUsers
           */
          var onGet = function (evt) {
            console.log('in onGet');
            var self = this;
            User2Model.find(function (err, users) {
              if (err) {
                console.log('\tonGet emits an error');
                return self.emit('error', {
                  res:evt.res,
                  req:evt.req,
                  where: 'User2Model.find',
                  err: err
                });
              }
              self.emit('processUsers', {
                //req:req,
                //res:res,
                users: users
              });
            });
          };
          
          /**
           * Handler called to process users array returned from User2Model.find
           *
           * @param {App#event:processUsers} evt - event object
           * @property {object} req - http request
           * @property {object} res - http response
           * @property {Array} users - array of Users
           *
           * @listens {App#event:processUsers}
           * @fires {App#event:nextUser}
           */
          var onProcessUsers = function (evt) {
            console.log('in onProcessUsers: %s', util.inspect(evt));
            var self = this;
            evt.last_id = null;
            evt.result = {};
            self.emit('nextUser', evt);
          };
          
          /**
           * Handler called to process a single user
           *
           * @param evt
           * @property {Array} users
           * @property {*} last_id
           * @property {object} result
           *
           * @listens {App#event:nextUser}
           * @emits {App#event:nextUser}
           * @emits {App#event:complete}
           */
          var onNextUser = function (evt) {
            var self = this;
          
            console.log('in onNextUser: %s', util.inspect(evt));
          
            if (!(Array.isArray(evt.users) && evt.users.length > 0)) {
              return self.emit('complete', evt);
            }
          
            var user = evt.users.shift();
          
            evt.last_id = user._id;
          
            evt.result[evt.last_id] = user;
          
            self.emit('nextUser', evt);
          };
          
          /**
           * Handler invoked when processing is complete.
           *
           * @param evt
           * @property {Array} users
           * @property {*} last_id
           * @property {object} result
           */
          var onComplete = function (evt) {
            console.log('in onComplete: %s', util.inspect(evt));
          };
          
          // main entry point
          
          var eventHandlers = { // map our handlers to events
            error: onError,
            get: onGet,
            processUsers: onProcessUsers,
            nextUser: onNextUser,
            complete: onComplete
          };
          
          var app = new App(eventHandlers); // create our test runner.
          
          app.get('/tp/show/method', function (req, res) { // and invoke it.
            app.emit('get', {
              req: req,
              res: res
            });
            /* note:
                 For this example, req and res are added to the evt
                 but are ignored.
          
                 In a working application, they would be used to
                 return a result or an error, should the need arise,
                 via res.send().
             */
          });
          

          结果

          in app.get
          in onGet
          in onProcessUsers: { users: [ { _id: 1 }, { _id: 2 } ] }
          in onNextUser: { users: [ { _id: 1 }, { _id: 2 } ], last_id: null, result: {} }
          in onNextUser: { users: [ { _id: 2 } ],
              last_id: 1,
              result: { '1': { _id: 1 } } }
          in onNextUser: { users: [],
              last_id: 2,
              result: { '1': { _id: 1 }, '2': { _id: 2 } } }
          in onComplete: { users: [],
              last_id: 2,
              result: { '1': { _id: 1 }, '2': { _id: 2 } } }
          

答案 2 :(得分:0)

好吧,我明白了。如果函数AdrModel.find是 async ,则您始终将此值设置为最后一个用户。

这是因为在for block end之后将执行异步函数。因此,所有AdrModel.find调用中user_id的值将始终相同,因为执行异步调用的已保存范围。假设您的用户是此集合

[{_id: 0}, {_id:2}, {_id: 3}]

因此,AdrModel.find的调用将始终使用user_id - > 3值:

ret[users[user_id]._id]=adr; //this guy will use user_id == 3, three times

修改
要解决您的问题很简单,模块化您的代码。 创建一个函数来执行此资源收集:

function setAdr(userId){
    AdrModel.find({ 'user_id': userId },function(err, adr) {
        ...
    }
}

然后,你在'for'中称呼它:

...
for(var user_id in users){
    setAdr(users[user_id].id_adr);
    ...

这样您就可以为每个异步调用保存一个安全范围。