async function fetchData(pathOptions, routerOptions, locals) {
    pathOptions = pathOptions || {};
    routerOptions = routerOptions || {};

    let postQuery = _.cloneDeep(defaultPostQuery);
    let promises = [];

    if (routerOptions.filter) {
        postQuery.options.filter = routerOptions.filter;
    }

    if (routerOptions.order) {
        postQuery.options.order = routerOptions.order;
    }

    if (Object.prototype.hasOwnProperty.call(pathOptions, 'page')) {
        postQuery.options.page = pathOptions.page;
    }

    if (Object.prototype.hasOwnProperty.call(pathOptions, 'limit')) {
        postQuery.options.limit = pathOptions.limit;
    }

    // CASE: always fetch post entries
    // The filter can in theory contain a "%s" e.g. filter="primary_tag:%s"
    promises.push(processQuery(postQuery, pathOptions.slug, locals));

    // CASE: fetch more data defined by the router e.g. tags, authors - see TaxonomyRouter
    _.each(routerOptions.data, function (query, name) {
        const dataQueryOptions = _.merge(query, defaultDataQueryOptions[name]);
        promises.push(processQuery(dataQueryOptions, pathOptions.slug, locals));
    });

    const results = await Promise.all(promises);
    const response = _.cloneDeep(results[0]);

    if (routerOptions.data) {
        response.data = {};

        let resultIndex = 1;

        _.each(routerOptions.data, function (config, name) {
            if (results[resultIndex]) {
                response.data[name] = results[resultIndex][config.resource];

                if (config.type === 'browse') {
                    response.data[name].meta = results[resultIndex].meta;
                }

                resultIndex = resultIndex + 1;
            }
        });
    }

    return response;
}
Fetch data from API helper for controllers.

Calls out to get posts per page, builds the final posts query & builds any additional queries
Uses Promise.all to handle the queries and ensure concurrent execution.
Does a first round of formatting on the response, and returns