Javascript 执行顺序问题
在我下面的代码中,5总是在4之前打印。我认为,因为对Javascript 执行顺序问题,javascript,Javascript,在我下面的代码中,5总是在4之前打印。我认为,因为对positioners的回调在matchAgainstAD的返回语句中,它将等待for循环和ad查找完成后再返回。我怎样才能以最简单的方式做到这一点 var matchAgainstAD = function(stUsers) { stUsers.forEach(function (element, i) { var sAMAccountName = stUsers[i].guiLoginName; // Find u
positioners
的回调在matchAgainstAD
的返回语句中,它将等待for循环和ad查找完成后再返回。我怎样才能以最简单的方式做到这一点
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();
ad.findUser
接受包含console.log(4)
的回调。该函数是异步的,当IO操作完成时,它将命中您的回调
另一方面,positioners
是完全同步调用的,因此在ad.findUser
进入回调之前,它将点击console.log(5)
解决此问题的一个简单方法是从ad.findUser
回调内部调用positioners
我建议研究JavaScript的承诺模式来管理异步操作之间的依赖关系。有几种流行的库(Q和RSVSP.js是其中的一对)。这是node.js中非常典型的异步问题。您的
findUser()
函数有一个异步响应,这意味着回调将在稍后调用。同时,循环的其余部分将继续运行,以便所有请求都同时进行,然后响应将在稍后某个时间开始。因此,在matchAgainstAd()
返回后不能调用positioners()
,因为内部异步操作尚未完成,因此staleUsers
尚未填充
解决这个问题有多种方法。一般来说,值得学习如何为这样的操作使用承诺,因为它们在使用异步操作时提供了各种非常有用的控制流选项,node.js几乎将所有I/O操作都作为异步操作来执行。但是,为了最好地说明这类问题中发生了什么,我将首先向您展示一个手动编码的解决方案。在这个手动编码的解决方案中,您可以跟踪还有多少操作要完成,以及何时所有操作都已完成,然后,也只有在那时,您才能使用累积的数据调用positioners()
手动编码的解决方案
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
数组现在已完全填充,您可以调用postures(staleUsers)
来处理累积结果
使用蓝鸟承诺编码的解决方案
这里的想法是,我们使用控制流逻辑和承诺的增强错误处理来管理异步控制流。这是通过“提示”我们在这里使用的每个异步接口来实现的。“Promisizing”是围绕遵循node.js调用约定的任何异步函数创建一个小函数包装器的过程,其中函数的最后一个参数是一个回调,它至少接受两个参数,第一个参数是错误,第二个参数是值。这可以自动转换为一个包装函数,返回一个承诺,允许使用承诺逻辑流进行任何正常的异步操作
下面是使用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承诺版本
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);
})
}
这里有一个版本,它只使用node.js中内置的标准ES6承诺。这里的主要区别在于,您必须编写您自己想要使用的异步函数的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
})
active directory接口是异步的。操作完成时将调用回调。JavaScript通常没有“等待”的概念。关于解决类似问题的各种选项的讨论,请参见下面的答案:我现在明白了,我的问题是我不知道如何纠正它。意思是,在ad.findUser
完成后,我如何使5执行?@CiscoKidx这个答案是正确的。我建议您在forEach
中创建一个计数器,当计数器到达stUsers
的长度时,您可以调用postasters(staleUsers)
。这将是最后一次迭代。谢谢你的回答。一个承诺的例子(在这种情况下)会很有帮助。我担心这就是解决方案。@CiscoKidx-我添加了使用蓝鸟承诺的第二个版本。@CiscoKidx-我添加了使用标准ES6承诺的第三个版本。