我们有一系列人物和一系列地址。在每个人中,都有一个地址的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"这样的新属性,它就无法工作。我错过了什么?
答案 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"的痛苦我们更倾向于使用事件,因为它们是一种更自然的方式让我思考这类问题。
代码首先定义了两个模拟:
get
方法的App类,允许我们模拟OP的app
实例,find
函数。然后记录以下事件:
error
- 在任何错误上调用以将消息打印到控制台并退出程序get
- 使用app.get
方法的结果触发,并使用{req:req,res:res}立即触发processUsers
事件processUsers
- 由get
事件处理程序使用模拟的用户对象数组触发,设置结果对象和last_id值,然后调用nextUser
事件。nextUser
- 由processUsers
事件触发,该事件从users数组中选择下一个用户,设置evt.last_id,将用户添加到evt.results,并自行发出,或者如果有没有用户留在evt.users数组上,发出complete
complete
- 由nextUser触发,只是将消息输出到控制台。接下来使用' + eventName约定来定义事件处理程序。
最后,我们
app
实例和get
方法,该回调只会发出get
事件以启动滚动。我使用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);
...
这样您就可以为每个异步调用保存一个安全范围。