Order of execution issue javascript -
in code below, 5 prints before 4. thought because callback postusers in return statement matchagainstad wait for loop , ad lookup complete before returning. how can in simple way?
var matchagainstad = function(stusers) { stusers.foreach(function (element, i) { var samaccountname = stusers[i].guiloginname; // find user 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();
this classic asynchronous problem in node.js. finduser() function has asynchronous response means callback called sometime later. meanwhile, rest of loop continues run requests in-flight @ same time , responses start coming in sometime later. thus, can't call postusers() after matchagainstad() returns because inner async operations not yet completed , staleusers not yet populated.
there multiple approaches solving problem. in general, worth learning how use promises operations because offer sorts of useful flow of control options when using asynchronous operations , node.js pretty i/o operations async. but, best illustrate what's going on in type of issue, i'll first show manually coded solution. in manually coded solution, keep track of how many operations still remaining complete , when have completed, , then, call postusers() accumulated data.
manually coded solution
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 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); }) } the core logic here initialize counter number of operations carried out. then, in loop each operation happening, decrement remaining counter anytime 1 of operations completes (calls it's completion callback). then, after processing result (in both success , error code paths), check if remaining count has reached 0 indicating requests done. if so, staleusers array populated , can call postusers(staleusers) process accumulated result.
solution coded using bluebird promises
the idea here use control flow logic , enhanced error handling of promises manage async control flow. done "promisifying" each asynchronous interface using here. "promisifying" process of creating small function wrapper around async function follows node.js calling convention last argument function callback takes @ least 2 arguments, first error , second value. can automatically turned wrapper function returns promise, allow using use promise logic flow normal async operation.
here's how work using bluebird promise library.
var promise = require('bluebird'); var request = promise.promisifyall(request('require')); var matchagainstad = function (stusers) { var staleusers = []; var ad = new activedirectory(config); // 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, consider staleuser if (!user) { staleusers.push(usertosearchfor); } }, function(err) { // purposely skip requests error on // having error handler nothing // stop promise propagation of error (it 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 }) standard es6 promises version
and, here's version uses standard es6 promises built-into node.js. main difference here have code own promisified versions of async functions want use because can't use built-in promisify capabilities in bluebird.
var request = request('require'); // make 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 function gets 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); // promisified version of finduser var finduser = getfinduserpromise(ad); stusers.each(function(usertosearchfor) { promises.push(finduser(usertosearchfor.guiloginname).then(function(user) { // if no user found, consider staleuser if (!user) { staleusers.push(usertosearchfor); } }, function(err) { // purposely skip requests error on // have error handler nothing // stop promise propagation of error (it 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 })
Comments
Post a Comment