如何解决异步 Node.js 行为?

How to get around the asynchronous Node.js behaviour?

我正在编写一个脚本,该脚本可以对网站执行 ping 操作,并 return 将结果保存在网络 UI 中。但是,我 运行 遇到了一个问题,我正在尝试找出最佳解决方案。

此代码块需要 return 状态数组,但由于 Node.js 的异步行为,它 return 是一个空数组,因为代码需要时间来执行.

这是我的资料:

var ping = require('ping');

function checkConnection(hosts) {

    var results = [];

    hosts.forEach(function (host) {
        ping.sys.probe(host, function (isAlive) {
            results.push({"host": host, "status": isAlive});
        });
    });

    return {results: results, timestamp: new Date().getTime()};
}

module.exports.checkConnection = checkConnection;

我知道您可以使用计时器解决这个问题,但最简单和最理想的解决方案是什么?

How to get around the asynchronous Node.js behaviour?

不要。相反,拥抱它,让你的checkConection接受回调或return承诺。

回调示例:

function checkConnection(hosts, callback) {

    var results = [];

    hosts = hosts.slice(0); // Copy
    hosts.forEach(function (host) {
        ping.sys.probe(host, function (isAlive) {
            results.push({"host": host, "status": isAlive});
            if (results.length === hosts.length) {
                callback({results: results, timestamp: new Date().getTime()});
            }
        });
    });
}

注意 hosts 的防御性浅拷贝。如果您不这样做,那么由于此代码异步运行,调用代码可能会在您处理响应时向 hosts 数组添加或从中删除,并且长度永远不会匹配。

另一种无需复制即可处理该问题的方法是简单地计算您发起的请求数量:

function checkConnection(hosts, callback) {

    var results = [];
    var requests = hosts.length;

    hosts.forEach(function (host) {
        ping.sys.probe(host, function (isAlive) {
            results.push({"host": host, "status": isAlive});
            if (results.length === requests) {
                callback({results: results, timestamp: new Date().getTime()});
            }
        });
    });
}

看起来像它设置了一个竞争条件(如果在您设置 requests 之后但在您完成启动之前修改 hosts 怎么办probe 查询?)但它没有,因为 Node 在单个线程上运行你的 JavaScript,所以没有其他代码可以进入并修改 requests = hosts.lengthhosts 之间的 hosts hosts.forEach 行。

喜欢T.J。说,如果你打算在 node.js 中编程,你将需要拥抱异步行为,因为这是它如何工作以及如何使用 node.js.[=27 编写响应迅速、可扩展的服务器的基本原则=]

T.J. 的回答是解决这个特定问题的直接方法。但是,由于异步问题会在 node.js 中反复出现,因此 promises 可以成为管理异步行为的非常有用的工具,并且它们很快成为具有强大错误处理能力的更复杂的多操作序列不可或缺的。

因此,这是使用 Promises 解决您的编码问题的方法:

var ping = require('ping');
var Promise = require('bluebird');

// make a version of ping.sys.probe that returns a promise when done
ping.sys.probeAsync = function(host) {
    return new Promise(function(resolve, reject) {
        ping.sys.probe(host, function(isAlive) {
            resolve({"host": host, "status": isAlive});
        });
    }
}

function checkConnection(hosts) {
    var promises = hosts.map(function(host) {
        return ping.sys.probeAsync(host);
    });
    return Promise.all(promises).then(function(results) {
        return {results: results, timestamp: new Date().getTime()};
    });
}

module.exports.checkConnection = checkConnection;

示例用法:

myModule.checkConnection(myArrayOfHosts).then(function(results) {
    // results is the {results: results, timestamp: time} object
});

循序渐进,这是它的工作原理:

  1. 加载 Bluebird promise 库。

  2. 创建一个名为 ping.sys.probeAsyncping.sys.probe 的承诺版本,returns 承诺将在底层调用完成时解决。

  3. 在数组上使用 .map(),通过对数组中的每个项目调用 ping.sys.probeAsync 创建一个承诺数组。

  4. 使用Promise.all(),创建一个新的承诺,它是数组中所有承诺的聚合。只有当数组中的所有承诺都已解决(例如已完成)时,它才会调用它的 .then() 处理程序。

  5. .then() 处理程序添加到 Promise.all(),以便可以将时间戳添加到结果中。

  6. Return Promise.all() 承诺,因此 checkConnection() 的调用者可以获得他们可以使用的承诺。

  7. 调用 checkConnection() 时使用 .then() 处理程序来了解所有操作何时完成并获取结果。


希望您能看到,一旦您拥有函数的承诺版本并且了解承诺的工作原理,您就可以更简单地编写实际的异步代码。而且,如果您还有错误处理或一系列异步操作必须 运行 一个接一个(这里没有),那么使用 promises 的优势就更大了。


P.S。我认为 Bluebird 的 Promise.map() 可以用于将 hosts.map()Promise.all() 组合成一个调用,但我自己没有使用过该功能,所以我没有在这里提供它。