dealing with an array of objects with promises

与世无争的帅哥 提交于 2020-05-24 03:51:47

问题


I am trying to make a node express app where I fetch data from different url's making a call to node-fetch to pull the body of some pages and other information about certain url endpoints. I want to then render a html table to display this data through an array of information. I am having trouble with the call to render the information as all the functions are asynchronous making it difficult to make sure all the promise calls have been resolved before making my call to render the page. I have been looking into using bluebird and other promise calls of .finally() and .all() but they don't seem to work on my data as it is not an array of promise calls, but an array of objects. Each object was 4 promise calls to fetch data relating to a column of my table all in one row. Is there a function or specific way to render the page after all promises are resolved?

var express = require('express');
var fetch = require('node-fetch');
fetch.Promise = require('bluebird');
var router = express.Router();
const client = require('../platform-support-tools');


function makeArray() {
    var registry = client.getDirectory();

    var data_arr = [];
    for (var i = 0; i < registry.length; i++) {
        var firstUp = 0;
        for (var j = 0; i < registry[i]; j++) {
            if (registry[i][j]['status'] == 'UP') {
                firstUp = j;
                break;
            }
        }
        var object = registry[i][firstUp];
        
        data_arr.push({
            'name': object['app'],
            'status': object['status'],
            'swagUrl': object['homePageUrl'] + 'swagger-ui.html',
            'swag': getSwag(object),
            'version': getVersion(object['statusPageUrl']),
            'timestamp': getTimestamp(object['statusPageUrl']),
            'description': getDescription(object['healthCheckUrl'])
        });
    }
    return data_arr;
}

function getSwag(object_in) {
    var homeUrl = object_in['homePageUrl'];
    if (homeUrl[homeUrl.length - 1] != '/'){
        homeUrl += '/';
    }
    var datum = fetch(homeUrl + 'swagger-ui.html')
        .then(function (res) {
            return res.ok;
        }).catch(function (err) {
            return 'none';
        });
    return datum;
}


function getVersion(url_in) {
    var version = fetch(url_in)
        .then(function(res) {
            return res.json();
        }).then(function(body) {
            return body['version'];
        }).catch(function (error) {
            return 'none';
        });
    return version;
}

function getTimestamp(url_in) {
    var timestamp = fetch(url_in)
        .then(function(res) {
            return res.json();
        }).then(function(body) {
            return body['timestamp'];
        }).then(function (res) {
            return body['version'];
        }).catch(function (error) {
            return 'none';
        });
    return timestamp;
}

function getDescription(url_in) {
    var des = fetch(url_in)
        .then(function(res) {
            return res.json();
        }).then(function(body) {
            return body['description'];
        }).catch(function (error) {
            return 'none';
        });
    return des;
}


/* GET home page. */
router.get('/', function (req, res, next) {
    var data_arr = makeArray();
    
    Promise.all(data_arr)
        .then(function (response) {
            //sorting by app name alphabetically
            response.sort(function (a, b) {
                return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
            });
            res.render('registry', {title: 'Service Registry', arr: response})
        }).catch(function (err) {
        console.log('There was an error loading the page: '+err);
    });
});

回答1:


To wait on all those promises, you will have to put them into an array so you can use Promise.all() on them. You can do that like this:

let promises = [];
for (item of data_arr) {
    promises.push(item.swag);
    promises.push(item.version);
    promises.push(item.timestamp);
    promises.push(item.description);
}
Promise.all(promises).then(function(results) {
    // all promises done here
})

If you want the values from all those promises, back into the object that's a bit more work.

let promises = [];
for (item of data_arr) {
    promises.push(item.swag);
    promises.push(item.version);
    promises.push(item.timestamp);
    promises.push(item.description);
}
Promise.all(promises).then(function(results) {
    // replace promises with their resolved values
    let index = 0;
    for (let i = 0; i < results.length; i += 4) {
         data_arr[index].swag = results[i];
         data_arr[index].version = results[i + 1];
         data_arr[index].timestamp = results[i + 2];
         data_arr[index].description = results[i + 3];
         ++index;
    });
    return data_arr;
}).then(function(data_arr) {
    // process results here in the array of objects
});

If you had to do this more often that just this once, you could remove the hard coding of property names and could iterate all the properties, collect the property names that contain promises and automatically process just those.


And, here's a more general version that takes an array of objects where some properties on the objects are promises. This implementation modifies the promise properties on the objects in place (it does not copy the array of the objects).

function promiseAllProps(arrayOfObjects) {
    let datum = [];
    let promises = [];

    arrayOfObjects.forEach(function(obj, index) {
        Object.keys(obj).forEach(function(prop) {
            let val = obj[prop];
            // if it smells like a promise, lets track it
            if (val && val.then) {
                promises.push(val);
                // and keep track of where it came from
                datum.push({obj: obj, prop: prop});
            }
        });
    });

    return Promise.all(promises).then(function(results) {
        // now put all the results back in original arrayOfObjects in place of the promises
        // so now instead of promises, the actaul values are there
        results.forEach(function(val, index) {
            // get the info for this index
            let info = datum[index];
            // use that info to know which object and which property this value belongs to
            info.obj[info.prop] = val;
        });
        // make resolved value be our original (now modified) array of objects
        return arrayOfObjects;
    });
}

You would use this like this:

// data_arr is array of objects where some properties are promises
promiseAllProps(data_arr).then(function(r) {
    // r is a modified data_arr where all promises in the 
    // array of objects were replaced with their resolved values
}).catch(function(err) {
    // handle error
});

Using the Bluebird promise library, you can make use of both Promise.map() and Promise.props() and the above function would simply be this:

function promiseAllProps(arrayOfObjects) {
    return Promise.map(arrayOfObjects, function(obj) {
        return Promise.props(obj);
    });
}

Promise.props() iterates an object to find all properties that have promises as values and uses Promise.all() to await all those promises and it returns a new object with all the original properties, but the promises replaced by the resolved values. Since we have an array of objects, we use Promise.map() to iterate and await the whole array of those.



来源:https://stackoverflow.com/questions/45022279/dealing-with-an-array-of-objects-with-promises

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!