简体   繁体   中英

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.

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.

For example, my second promise: methods.login() returns a sessionId that is used in almost every other promise. How would I assign that variable and pass it to further dependent promises using 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.

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.
  4. writeToFile() is changed to return a promise.
  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)
  6. Swap the two conditions when checking apiVersion because you should check !apiVersion first.

Promises.all is intended to handle asynchronous functions when they run in parallel and you need to wait until they all are finished. The promises in the code you shared are not independent so you can't use Promises.all here. Check out this reference to see the Promises.all usage.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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