【问题标题】:Order of execution issue javascript执行顺序问题javascript
【发布时间】:2015-09-30 16:45:30
【问题描述】:

在我下面的代码中,5 总是在 4 之前打印。我认为因为对 postUsers 的回调位于来自 matchAgainstAD 的 return 语句中,它会等待 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();

【问题讨论】:

  • 活动目录接口是异步的。操作完成时将调用回调。 JavaScript 一般没有“等待”的概念。
  • 有关解决类似问题的各种选项的讨论,请参阅此答案:stackoverflow.com/questions/32799672/…

标签: javascript


【解决方案1】:

这是 node.js 中非常经典的异步问题。您的 findUser() 函数具有异步响应,这意味着稍后会调用回调。同时,循环的其余部分继续运行,以便所有请求同时进行,然后响应开始在稍后的某个时间出现。因此,您不能在 matchAgainstAd() 返回后调用 postUsers(),因为内部异步操作尚未完成,因此尚未填充 staleUsers

有多种方法可以解决这个问题。一般来说,值得学习如何将 Promise 用于此类操作,因为它们在使用异步操作时提供了各种非常有用的控制流选项,并且 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 编码的解决方案

这里的想法是我们使用控制流逻辑和增强的 Promise 错误处理来管理异步控制流。这是通过“承诺”我们在这里使用的每个异步接口来完成的。 “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 Promises 版本

而且,这是一个仅使用标准 ES6 Promise 内置于 node.js 的版本。这里的主要区别在于,您必须编写自己的 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
})

【讨论】:

  • @CiscoKidx - 我添加了使用 Bluebird 承诺的第二个版本。
  • @CiscoKidx - 我添加了使用标准 ES6 承诺的第三个版本。
【解决方案2】:

ad.findUser 接受一个包含console.log(4) 的回调。该函数是 async,当 IO 操作完成时会触发您的回调。

另一方面,postUsers 是完全同步调用的,所以它会在ad.findUser 进入你的回调之前命中console.log(5)

解决此问题的一种简单方法是从您的 ad.findUser 回调内部调用 postUsers

我建议研究 JavaScript 的 promise 模式 来管理异步操作之间的依赖关系。有几个流行的库(Q 和 RSVSP.js 是其中的几个)。

【讨论】:

  • 我现在明白了,我的问题是我不知道如何纠正它。意思是,ad.findUser 完成后如何让 5 执行?
  • @CiscoKidx 这个答案是正确的。我建议你在forEach 内创建一个计数器,当计数器达到stUsers 的长度时,你调用postUsers(staleUsers)。这将是最后一次迭代。
  • 感谢您的回答。一个承诺的例子(在这种情况下)真的很有帮助。我担心这将是解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
  • 2015-12-22
  • 2016-09-11
相关资源
最近更新 更多