在我的下面的代码中,5总是在4之前打印。我想因为postUsers
的回调是在matchAgainstAD
的返回语句中,它会在返回之前等待for循环和广告查找完成。我怎样才能以最简单的方式做到这一点?
var matchAgainstAD = function(stUsers) {
stUsers.forEach(function (element, i) {
var sAMAccountName = stUsers[i].guiLoginName;
// Find user by a sAMAccountName
var ad = new ActiveDirectory(config);
ad.findUser(sAMAccountName, function(err, user) {
if (err) {
console.log('ERROR: ' +JSON.stringify(err));
return;
}
if (!user) {
staleUsers.push(stUsers[i])
console.log(4)
}
// console.log(staleUsers);
});
})
return postUsers(staleUsers)
}
var postUsers = function(staleUsers) {
console.log(5);
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);
})
}
matchAgainstAD();
答案 0 :(得分:3)
这是node.js中非常经典的异步问题。您的findUser()
函数具有异步响应,这意味着稍后会调用回调。同时,循环的其余部分继续运行,以便所有请求同时在飞行中,然后响应在稍后的某个时间开始。因此,在postUsers()
返回后您无法调用matchAgainstAd()
,因为内部异步操作尚未完成,因此尚未填充staleUsers
。
有多种方法可以解决这个问题。一般来说,值得学习如何将promises用于此类操作,因为它们在使用异步操作时提供了各种非常有用的控制选项流,而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编码的解决方案
这里的想法是我们使用控制流逻辑和增强的promises错误处理来管理异步控制流。这是通过" promisifying"我们在这里使用的每个异步接口。 " 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承诺版本
而且,这里只使用内置于node.js的标准ES6 promises的版本。这里的主要区别在于您必须编写自己要使用的异步函数的promisified版本,因为您无法使用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
})
答案 1 :(得分:2)
ad.findUser
接受包含console.log(4)
的回调。该功能是 async ,并在IO操作完成后点击您的回调。
另一方面,postUsers
被完全同步调用,因此在console.log(5)
进入回调之前它会点击ad.findUser
。
解决此问题的一种简单方法是从postUsers
回调内拨打ad.findUser
。
我建议使用JavaScript的 promise模式来管理异步操作之间的依赖关系。有几个流行的库(Q和RSVSP.js是其中的几个)。