执行顺序问题javascript

时间:2015-09-30 16:45:31

标签: javascript

在我的下面的代码中,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();

2 个答案:

答案 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是其中的几个)。