简体   繁体   中英

How to handle multiple api calls in a time-constrained scenario?

I have to make multiple API calls in as short a time as possible. The need to make multiple calls arises from me having to populate a wide range of conditional data sets. Say I have n metrics, each to be filtered with m possible filters. I would want to get the results.totalsForAllResults for the n*m queries I generate.

While I faced a lot of hiccups initially, knowing about closures solved many issues about my trouble with sending async API calls. I was even able to handle results well, in the proper order. Now I'm facing an issue where the maximum number of API requests per second poses an issue for me. Google Core Reporting API v3 allows a maximum of 10 API requests in a second, and I'm well past this limit.

Here is how I tried to make the API calls and process the responses. I have little freedom with the structure:

function getMetrics() {
    initResultsArray();
    for (mI = 0; mI < metricsArray.length; mI++) {
        (function (mI) {    //Closure. Holds a local value of 'mI' within the scope
            for (fI = 0; fI < filtersArray.length; fI++) { 
                (function (fI) {   //Closure. Holds a local value of 'fI' within the scope
                    gapi.client.analytics.data.ga.get({
                        'ids': tableID,
                        'start-date': startDate,
                        'end-date': endDate,
                        'metrics': metricsArray[mI],
                        'filters': filtersArray[fI],
                        'samplingLevel': 'HIGHER_PRECISION',
                    }).execute(function putToVar(results) {   //this fn is defined inline to get access to fI
                        console.log(results.totalsForAllResults);
                        resultsArray[mI][fI] = parseInt(results.totalsForAllResults[metricsArray[mI]]);
                    });
                })(fI); //This ends the closure for fI
            }
        })(mI); //This ends the closure for mI
    }
}
//Print results to console, called when I believe all results have been populated.
function logResults() {
    console.log(resultsArray);
}

I need to be able to find out if I have made 10 queries in the last second and wait to send the remaining queries, because as soon as I exceed 10 queries per second I get null objects as response for my API calls and it ruins my ability to retrieve values into arrays. How can this be done? I do not know how to use wait() and people say the browser becomes unresponsive if you use wait(), and I don't know how setTimeOut() can be applied to my problem.

mI and fI are global iterators for metrics and filters and metricsArray and filtersArray are arrays of strings representing metrics and filters in the way GA API would expect them, I just need to iterate through them to obtain a lot of results.TotalsForAllResults . There is no problem with the execution of the API calls and responses. My only issue is exceeding the 10 queries per second limit and not getting further responses.

You could solve this by first creating a list of the calls you need to make, then making them 10 at a time. This is all off the cuff, so no guarantees that it actually works but hopefully you can apply it to your situation.

The general idea is to create a simple Scheduler constructor function that takes in an array of stuff to process. Naming stuff more descriptively would be better :). The created object has a single function - start .

var Scheduler = function (stuffToProcess) {
    var started,
        executeFunction;

    getExecuteFunction = function (current) {
        return function (results) {
            console.log(results.totalsForAllResults);
            resultsArray[current.mI][current.fI] = parseInt(results.totalsForAllResults[metricsArray[current.mI]], 10);
        };
    }

    var processNext = function () {
        var current = stuffToProcess.shift(),
            counter = 0;

        while (current && ++counter <= 10) {
            gapi.client.analytics.data.ga
                .get(current.gaBit)
                .execute(getExecuteFunction(current));
            if (counter !== 10) {
                current = stuffToProcess.shift(); // <- EDIT: Forgot this in original answer.
            }
        }
        if (stuffToProcess.length > 0) {
            window.setTimeout(function () {
                processNext();
            }, 1000);
        }
    };

    this.start = function () {
        if (!started) {
            started = true;
            processNext();
        }
    };    
};

Then in your getMetrics function, instead of calling ga directly, you build an array of the calls you want to make, then create a scheduler instance and start it.

function getMetrics() {
    initResultsArray();
    var listOfCalls = [];
    for (mI = 0; mI < metricsArray.length; mI++) {
        for (fI = 0; fI < filtersArray.length; fI++) { 
            listOfCalls.push({
                gaBit: {
                    'ids': tableID,
                    'start-date': startDate,
                    'end-date': endDate,
                    'metrics': metricsArray[mI],
                    'filters': filtersArray[fI],
                    'samplingLevel': 'HIGHER_PRECISION'
                },
                mI: mI,
                fI: fI
            });
        }
    }
    var s = new Scheduler(listOfCalls);
    s.start();
}

EDIT: Modified code to use getExecuteFunction instead.

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