这是 node.js 中非常经典的异步问题。您的 findUser() 函数具有异步响应,这意味着稍后会调用回调。同时,循环的其余部分继续运行,以便所有请求同时进行,然后响应开始在稍后的某个时间出现。因此,您不能在 matchAgainstAd() 返回后调用 postUsers(),因为内部异步操作尚未完成,因此尚未填充 staleUsers。
有多种方法可以解决这个问题。一般来说,值得学习如何将 Promise 用于此类操作,因为它们在使用异步操作时提供了各种非常有用的控制流选项,并且 node.js 几乎将所有 I/O 操作都作为异步操作。但是,为了最好地说明这类问题的情况,我将首先向您展示一个手动编码的解决方案。在这个手动编码的解决方案中,您可以跟踪还有多少操作尚待完成,以及何时完成所有操作,然后才使用累积的数据调用postUsers()。
手动编码解决方案
var matchAgainstAD = function (stUsers) {
var remaining = stUsers.length;
stUsers.forEach(function (element, i) {
var sAMAccountName = stUsers[i].guiLoginName;
function checkDone() {
if (remaining === 0) {
postUsers(staleUsers);
}
}
// Find user by a sAMAccountName
var ad = new ActiveDirectory(config);
ad.findUser(sAMAccountName, function (err, user) {
--remaining;
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
checkDone();
return;
}
if (!user) {
staleUsers.push(stUsers[i])
}
checkDone();
});
});
}
var postUsers = function(staleUsers) {
request.post({
headers: {'content-type' : 'application/x-www-form-urlencoded'},
url: 'http://localhost:8000/api/record/newRecord',
qs: staleUsers
}, function(err, res, body) {
// console.log(body);
})
}
这里的核心逻辑是你初始化一个计数器来计算将要执行的操作的数量。然后,在每个操作发生的循环中,只要其中一个操作完成(称为完成回调),您就递减 remaining 计数器。然后,在处理结果后(在成功和错误代码路径中),检查remaining 计数是否已达到 0,表示所有请求现在都已完成。如果是这样,那么 staleUsers 数组现在已完全填充,您可以调用 postUsers(staleUsers) 来处理累积的结果。
使用 Bluebird Promises 编码的解决方案
这里的想法是我们使用控制流逻辑和增强的 Promise 错误处理来管理异步控制流。这是通过“承诺”我们在这里使用的每个异步接口来完成的。 “promisifying”是围绕遵循 node.js 调用约定的任何异步函数创建一个小函数包装器的过程,其中函数的最后一个参数是一个回调,它至少需要两个参数,第一个是错误,第二个是值.这可以自动转换为返回 promise 的包装函数,允许将 promise 逻辑流与任何正常的异步操作一起使用。
这是使用 bluebird promise 库的工作原理。
var Promise = require('bluebird');
var request = Promise.promisifyAll(request('require'));
var matchAgainstAD = function (stUsers) {
var staleUsers = [];
var ad = new ActiveDirectory(config);
// get promisified version of findUser
var findUser = Promise.promisify(ad.findUser, ad);
return Promise.map(stUsers, function(userToSearchFor) {
var sAMAccountName = userToSearchFor.guiLoginName;
return findUser(sAMAccountName).then(function(user) {
// if no user found, then consider it a staleUser
if (!user) {
staleusers.push(userToSearchFor);
}
}, function(err) {
// purposely skip any requests we get an error on
// having an error handler that does nothing will
// stop promise propagation of the error (it will be considered "handled")
});
}).then(function() {
if (staleUsers.length) {
return postUsers(staleUsers);
}
return 0;
});
}
var postUsers = function (staleUsers) {
return request.postAsync({
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
url: 'http://localhost:8000/api/record/newRecord',
qs: staleUsers
}).spread(function (res, body) {
// console.log(body);
return staleUsers.length;
})
}
matchAgainstAD(users).then(function(qtyStale) {
// success here
}, function(err) {
// error here
})
标准 ES6 Promises 版本
而且,这是一个仅使用标准 ES6 Promise 内置于 node.js 的版本。这里的主要区别在于,您必须编写自己的 Promisify 版本的异步函数,因为您不能使用 Bluebird 中内置的 Promisify 功能。
var request = request('require');
// make a promisified version of request.post
function requestPostPromise(options) {
return new Promise(function(resolve, reject) {
request.post(options, function(err, res, body) {
if (err) {
reject(err);
} else {
resolve([res, body]);
}
});
});
}
// make a function that gets a promisified version of ad.findUser
function getfindUserPromise(ad) {
return function(name) {
return new Promise(function(resolve, reject) {
ad.findUser(name, function(err, user) {
if (err) {
reject(err);
} else {
resolve(user);
}
});
});
}
}
var matchAgainstAD = function (stUsers) {
var staleUsers = [];
var promises = [];
var ad = new ActiveDirectory(config);
// get promisified version of findUser
var findUser = getFindUserPromise(ad);
stUsers.each(function(userToSearchFor) {
promises.push(findUser(userToSearchFor.guiLoginName).then(function(user) {
// if no user found, then consider it a staleUser
if (!user) {
staleusers.push(userToSearchFor);
}
}, function(err) {
// purposely skip any requests we get an error on
// have an error handler that does nothing will
// stop promise propagation of the error (it will be considered "handled")
}));
});
return Promise.all(promises).then(function() {
if (staleUsers.length) {
return postUsers(staleUsers);
}
return 0;
});
}
var postUsers = function (staleUsers) {
return requestPostPromise({
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
url: 'http://localhost:8000/api/record/newRecord',
qs: staleUsers
}).then(function (err, results) {
var res = results[0];
var body = results[1];
// console.log(body);
return staleUsers.length;
})
}
matchAgainstAD(users).then(function(qtyStale) {
// success here
}, function(err) {
// error here
})