问题描述
在我的下面的代码中,5总是在4之前打印。我想因为 postUsers
的回调是在 matchAgainstAD $的返回语句中c $ c>在返回之前,它会等待for循环和广告查找完成。我怎么能以最简单的方式做到这一点?
In my code below, 5 always prints before 4. I thought because the callback to postUsers
was in a return statement from matchAgainstAD
it would wait for the for loop and ad lookup to complete before returning. How can I do this in the most simple way?
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();
推荐答案
这是node.js中非常经典的异步问题。你的 findUser()
函数有一个异步响应,这意味着稍后会调用回调。同时,循环的其余部分继续运行,以便所有请求同时在飞行中,然后响应在稍后的某个时间开始。因此,在 matchAgainstAd()
返回后,您无法调用 postUsers()
,因为内部异步操作尚未完成因此 staleUsers
尚未填充。
This is very classic asynchronous problem in node.js. Your findUser()
function has an asynchronous response which means the callback is called sometime later. Meanwhile, the rest of your loop continues to run so that all the requests are in-flight at the same time and then the responses start coming in sometime later. Thus, you can't call postUsers()
after matchAgainstAd()
returns because the inner async operations are not yet completed and thus staleUsers
is not yet populated.
有多种方法可以解决此问题。一般来说,值得学习如何将promises用于此类操作,因为它们在使用异步操作时提供了各种非常有用的控制选项流,而node.js几乎将所有I / O操作都作为异步执行。但是,为了最好地说明此类问题的发生,我将首先向您展示一个手动编码的解决方案。在这个手动编码的解决方案中,您可以跟踪仍有多少操作要完成,以及何时完成,然后只有这样,您是否使用以下方式调用 postUsers()
累积数据。
There are multiple approaches to solving this problem. In general, it is worth learning how to use promises for operations like this because they offer all sorts of very useful flow of control options when using asynchronous operations and node.js does pretty much all I/O operations as async. But, to best illustrate what's going on in this type of issue, I'll first show you a manually coded solution. In this manually coded solution, you keep track of how many operations are still remaining to complete and when all have completed, then and only then, do you call postUsers()
with the accumulated data.
手动编码解决方案
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);
})
}
这里的核心逻辑是你将计数器初始化为将要执行的操作数。然后,在每个操作发生的循环中,只要其中一个操作完成(调用它的完成回调),就会减少剩余的
计数器。然后,在处理结果(在成功和错误代码路径中)之后,检查剩余
计数是否已达到0表示现在所有请求都已完成。如果是这样,那么 staleUsers
数组现在已完全填充,您可以调用 postUsers(staleUsers)
来处理累积结果。
The core logic here is that you initialize a counter to the number of operations that will be carried out. Then, in your loop where each operation is happening, you decrement the remaining
counter anytime one of the operations completes (calls it's completion callback). Then, after processing the result (in both the success and error code paths), you check if the remaining
count has reached 0 indicating that all requests are now done. If so, then the staleUsers
array is now fully populated and you can call postUsers(staleUsers)
to process the accumulated result.
使用Bluebird Promises编码的解决方案
这里的想法是我们使用控制流逻辑和增强的错误处理承诺来管理异步控制流。这是通过promisifying我们在这里使用的每个异步接口来完成的。 promisifying是围绕node.js调用约定的任何异步函数创建一个小函数包装器的过程,其中函数的最后一个参数是一个至少有两个参数的回调,第一个是错误,第二个是一个值。这可以自动转换为一个返回promise的包装函数,允许使用promise逻辑流和任何正常的异步操作。
The idea here is that we use the control flow logic and enhanced error handling of promises to manage the async control flow. This is done by "promisifying" each asynchronous interface we are using here. "promisifying" is the process of creating a small function wrapper around any async function that follows the node.js calling convention where the last argument to the function is a callback that takes at least two arguments, the first an error and the second a value. This can be automatically turned into a wrapper function that returns a promise, allow using to use promise logic flow with any normal async operation.
以下是使用bluebird的方法承诺库。
Here's how this would work using the bluebird promise library.
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功能。
And, here's a version that uses only the standard ES6 promises built-into node.js. The main difference here is that you have to code your own promisified versions of the async functions you want to use because you can't use the built-in promisify capabilities in Bluebird.
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
})
这篇关于执行顺序问题javascript的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!