简体   繁体   English

NodeJS将Promises转换为Promise.all

[英]NodeJS Convert Promises to Promise.all

I have a NodeJS script that I have been working on, but the biggest problem I have is that chaining all of these promises is not only ugly to look at, but difficult to maintain as time goes on. 我有一个正在工作的NodeJS脚本,但是最大的问题是,将所有这些诺言链接起来不仅看起来很丑,而且随着时间的推移很难维护。

I want to convert these individual promises into one, using the Promise.all() method, but am not sure how I can get the same functionality and assign variables from one promise to another using this method. 我想使用Promise.all()方法将这些单独的承诺转换为一个,但是不确定如何使用该方法获得相同的功能并将变量从一个承诺分配给另一个承诺。

For example, my second promise: methods.login() returns a sessionId that is used in almost every other promise. 例如,我的第二个Promise: methods.login()返回一个几乎在所有其他Promise中使用的sessionId。 How would I assign that variable and pass it to further dependent promises using Promise.all() ?? 我将如何使用Promise.all()分配该变量并将其传递给其他相关的Promise.all()

Here is my current code: 这是我当前的代码:

var zabbixApi = require('./zabbixapi.js');
var methods   = require('./methods.js');
var fs        = require('fs');
var SESSIONID;

function main() {
    var apiVersion, loggedOut, numTemplates, numHosts, numHostGroups, numItems;
    var templates = []
        , hostGroups = []
        , hosts = [];

    /*var promises = [];
    promises.push(zabbixApi(methods.getApiVersion()));
    promises.push(zabbixApi(methods.login(SESSIONID)));
    promises.push(zabbixApi(methods.getHostGroups(SESSIONID)));
    promises.push(zabbixApi(methods.getTemplates(SESSIONID)));
    promises.push(zabbixApi(methods.getHosts(SESSIONID)));
   // promises.push(zabbixApi(methods.configExport(hostIds, templateIds, groupIds, SESSIONID)));
    promises.push(zabbixApi(methods.logout(SESSIONID)));

    Promise.all(promises).then(function (values) {
       console.log('All promises completed.');

    }, function (reason) {
        console.log('Error completing promises: ' + reason);
    });*/


    // Get API version
    zabbixApi(methods.getApiVersion())

    // If successful, login to the API
    .then(function (version) {
        apiVersion = version.result;

        // Verify that the API version returned is correct
        if (apiVersion.length < 5 || !apiVersion) {
            console.log('Error occurred retrieving API version: ' + version.error.data);
            return 1;
        } else {
            return zabbixApi(methods.login(SESSIONID));
        }

    }, function (error) {
        console.log('Error occurred retrieving API version: ' + error);
        return 1;

        // If login successful, continue operations until logged out or error
    }).then(function (auth) {
        SESSIONID = auth.result;

        if (!SESSIONID) {
            console.log('Error retrieving session id: ' + auth.error.data);
            return 1;
        } else {
            console.log('Logged in successfully!');
            return zabbixApi(methods.getHostGroups(SESSIONID));
        }

    }, function (error) {
        console.log('Error occurred authenticating: ' + error);
        return 1;

        // Attempt to retrieve all hostgroup information
    }).then(function (hostgroups) {
        numHostGroups = hostgroups.result.length;
        hostGroups = hostgroups.result;

        if (!numHostGroups) {
            console.log('Error occurred retrieving host groups: ' + hostgroups.error.data);
            return 1;
        } else {
            return zabbixApi(methods.getTemplates(SESSIONID));
        }


    }, function (error) {
        console.log('Error occurred retrieving host groups: ' + error);
        return 1;

        // Attempt to retrieve host information
    }).then(function (template) {
        numTemplates = template.result.length;
        templates = template.result;

        if (!numTemplates) {
            console.log('Error occurred retrieving templates: ' + template.error.data);
            return 1;
        } else {
            return zabbixApi(methods.getHosts(SESSIONID));
        }


    }, function (error) {
        console.log('Error occurred retrieving templates: ' + error);
        return 1;

        // Attempt to retrieve host information
    }).then(function (hosts) {
        numHosts = hosts.result.length;
        hosts    = hosts.result;

        if (!numHosts) {
            console.log('Error occurred retrieving host groups: ' + hosts.error.data);
            return 1;
        } else {
            var groupIds      = []
                , hostIds     = []
                , templateIds = [];

            // Extract all groupIds for host groups
            for (var i = 0; i < numHostGroups; i++) {
                groupIds[i] = hostGroups[i].groupid;
            }
            // Extract all hostIds for hosts
            for (var i = 0; i < numHosts; i++) {
                hostIds[i] = hosts[i].hostid;
            }
            // Extract all templateIds for templates
            for (var i = 0; i < numTemplates; i++) {
                templateIds[i] = templates[i].templateid;
            }
            return zabbixApi(methods.configExport(hostIds, templateIds, groupIds, SESSIONID));
        }

    }, function (error) {
        console.log('Error occurred retrieving host groups: ' + error);
        return 1;

        // Attempt to retrieve configuration information
    }).then(function (config) {
        //console.log(config);
        if (config.error) {
            console.log('Error occurred retrieving configuration: ' + config.error.message + ': ' + config.error.data);
            return 1;
        } else {
            if (!writeToFile(config)) {
                return 1;
            } else {
                console.log('Configuration details exported successfully.');

            }
            return zabbixApi(methods.logout(SESSIONID));
        }

    }, function (error) {
        console.log('Error occurred retrieving configuration: ' + error);
        return 1;

        // Attempt to logout of API, if logout successful exit safely
    }).then(function (logout) {
        loggedOut = logout.result;
        if (!loggedOut) {
            console.log('Error logging out: ' + logout.error.data);
            return 1;
        } else {
            console.log('Logged out successfully!');
            return 0;
        }

    }, function (error) {
        console.log('Error occurred logging out: ' + error);
        return 1;
    });
}

function writeToFile (config) {
    fs.writeFile('zabbix_config_export.json', JSON.stringify(config), function (err) {
        if (err) {
            return console.log('Error writing configuration to file: ' + err);
        }
    });
    return true;
}
main();

You can use Promise.all() on operations that can run in parallel, but not on operations that must be performed in a specific sequence. 您可以对可以并行运行的操作使用Promise.all() ,但不能对必须按特定顺序执行的操作使用。

In looking at your code, it appears that you do have a couple places where you can do some operations in parallel, but not all your operations can be done that way. 在查看代码时,似乎确实有几个地方可以并行执行一些操作,但并非所有操作都可以通过这种方式完成。 It appears you can do things in this general sequence: 看来您可以按照以下一般顺序进行操作:

getApiVersion  
Login to session  
In parallel (getHostGroups, getTemplates, getHosts)  
configExport previous results  
In parallel (logout, writeToFile) 

That can be achieved like this: 可以这样实现:

var zabbixApi = require('./zabbixapi.js');
var methods   = require('./methods.js');
var fs        = require('fs');
var SESSIONID;

function logout() {
    if (SESSIONID) {
        var p = zabbixApi(methods.logout(SESSIONID));
        // clear SESSIONID to show that we've already launched a logout attempt, no need to try again
        SESSIONID = null;
        return p;
    } else {
        return Promise.resolve();
    }
}

function main() {
    var apiVersion, hostGroups, templates, hosts;

    // Get API version
    zabbixApi(methods.getApiVersion())

    // If successful, login to the API
    .then(function (version) {
        apiVersion = version.result;

        // Verify that the API version returned is correct
        if (!apiVersion || apiVersion.length < 5) {
            throw new Error('Error occurred retrieving API version: ' + version.error.data);
        } else {
            return zabbixApi(methods.login(SESSIONID));
        }

    }, function (error) {
        throw new Error('Error occurred retrieving API version: ' + error);

    // If login successful, continue operations until logged out or error
    }).then(function (auth) {
        SESSIONID = auth.result;

        if (!SESSIONID) {
            throw new Error('Error retrieving session id: ' + auth.error.data);
        } else {
            console.log('Logged in successfully!');

            // now that we are logged in, a number of operations can be launched in parallel
            return Promise.all([
                zabbixApi(methods.getHostGroups(SESSIONID),
                zabbixApi(methods.getTemplates(SESSIONID),
                zabbixApi(methods.getHosts(SESSIONID)
            ]);
        }
    }, function (error) {
        throw new Error('Error occurred authenticating: ' + error);

    // we have hostGroups, templates and hosts here
    }).then(function(r) {
        // r[0] = hostGroups, r[1] = templates, r[2] = hosts

        // check hostGroups
        hostGroups = r[0].result;
        if (!hostGroups.length) {
            throw new Error('Error occurred retrieving host groups: ' + hostgroups.error.data);
        }

        // check templates
        templates = r[1].result;
        if (!templates.length) {
            throw new Error('Error occurred retrieving templates: ' + template.error.data);
        }

        // check host information
        hosts = r[2].result;
        if (!hosts.length) {
            throw new Error('Error occurred retrieving host groups: ' + hosts.error.data);
        }

        // utility function for retrieving a specific property from each array of objects
        function getIds(array, prop) {
            return array.map(function(item) {
                return item[prop];
            });
        }

        var groupIds = getIds(hostGroups, "groupid");
        var hostIds = getIds(hosts, "hostid");
        var templateIds = getIds(templates, "templateid");
        return zabbixApi(methods.configExport(hostIds, templateIds, groupIds, SESSIONID));
    }).then(function(config) {
        if (config.error) {
            throw new Error('Error occurred retrieving configuration: ' + config.error.message + ': ' + config.error.data);
        }
        // simultaneously write to file and logout (since these are not dependent upon one another)
        return Promise.all(logout(), writeToFile(config));
    }).then(function() {
        // success here, everything done
    }, function(err) {
        // upon error, try to logout and rethrow earlier error
        return logout().then(function() {
            throw err;
        }, function() {
            throw err;
        });
    }).then(null, function(err) {
        // error here
        console.log(err);
    });

}

function writeToFile (config) {
    return new Promise(function(resolve, reject) {
        fs.writeFile('zabbix_config_export.json', JSON.stringify(config), function (err) {
            if (err) {
                return Promise.reject(new Error('Error writing configuration to file: ' + err));
            }
            resolve();
        });
    });
}
main();

This also makes some other important structural changes/fixes: 这还会进行其他一些重要的结构更改/修复:

  1. Any time you have a reject/catch handler, if you don't either return a rejected promise or throw from that handler, then the promise chain will continue which is not what you want and you were doing that in almost every reject handler you had. 每当您有拒绝/捕获处理程序时,如果您既不返回被拒绝的承诺,也不从该处理程序中抛出,则承诺链将继续运行,这不是您想要的,并且您几乎在所有拥有的拒绝处理程序中都这样做。
  2. No need to have a reject handler for every single promise. 无需为每一个承诺都设置拒绝处理程序。 If you just intend for any reject to stop the chain anyway, then you can just make sure that the reject reason is descriptive and do all failure logging one place at the end of the chain. 如果您只是打算以任何拒绝方式终止链,则只需确保拒绝原因是描述性的,并在链末尾将所有故障记录在一个位置即可。
  3. This uses Promise.all() in two places where operations can run in parallel because they are not dependent upon one another, but the code logic wants to know when they are all done. 这在两个地方可以并行运行操作的地方使用Promise.all() ,因为它们彼此不依赖,但是代码逻辑想知道何时完成所有操作。
  4. writeToFile() is changed to return a promise. writeToFile()更改为返回承诺。
  5. Create a logout() function that can be safely called in several error paths so all error and success paths at least attempt the logout (if login succeeded) 创建一个可以在多个错误路径中安全调用的logout()函数,以便所有错误和成功路径至少都会尝试注销(如果登录成功)
  6. Swap the two conditions when checking apiVersion because you should check !apiVersion first. 检查apiVersion时,请交换两个条件,因为您应该首先检查!apiVersion

Promises.all is intended to handle asynchronous functions when they run in parallel and you need to wait until they all are finished. Promises.all用于在异步函数并行运行时处理它们,您需要等待它们全部完成。 The promises in the code you shared are not independent so you can't use Promises.all here. 您共享的代码中的Promise不是独立的,因此您不能在此处使用Promises.all Check out this reference to see the Promises.all usage. 查看此参考以查看Promises.all用法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM