简体   繁体   中英

How to get the last index in a JSON array when you don't know how large the array is going to be?

I have an array of JSON objects. This array is within a for loop and it keeps adding them to the array as the information becomes available from the database. The number of the objects in the array might not be the same as the number of results returned from the database (because only some locations might have the type of food a user is searching for).

For instance, I can have 12 restaurants returned in rows , however only 3 sell hamburgers, so I can't simply do if (rows.length - 1 == i) , because i is only going to reach 2 while rows.length - 1 is 11.

So the matching returned results (JSON) are added one by one in a for loop. I can never preemptively know how many restaurants sell burgers until all those restaurants that do are added to the array.

I've tried a variety of tricks and the usual error I get from node is "can't send headers more than once." And I know why it's giving me this error. It's giving me this error because every iteration of the loop it will return whatever it has in the array.

An example of output

First iteration:

{ "results": [ {"name_of_restaurant": "joes burgers", "open_now": true }] }

Second iteration:

{ "results": [ {"name_of_restaurant": "joes burgers", "open_now": true }, { "name_of_restaurant": "five guys", "open_now": true }] }

Third iteration:

{ "results": [ { "name_of_restaurant": "joes burgers", "open_now": true }, "{ name_of_restaurant": "five guys", "open_now": true }, " { name_of_restaurant": "shake shack", "open_now": true }] }

I want a way to capture the third iteration so I can send that back to the client.

To be clear, I am not looking for array.length - 1 . My problem is substantially more complex.

EDIT - code added

function retrieveLocation(callback) {
    var locationsWithinVisibleMapRectQuery = "SELECT * FROM locations WHERE Y(coordinates) > " + req.body.SWCoordLat + "AND Y(coordinates) < " + req.body.NECoordLat + "AND X(coordinates) > " + req.body.SWCoordLong + "AND X(coordinates) < " + req.body.NECoordLong + ";";
    connection.query(locationsWithinVisibleMapRectQuery, function(err, rows) {
        if (err) throw err; 

        var jsonObject = {
            "results" : []
        };

        //console.log("Number of businesses: " + rows.length);

        for(var i = 0; i < rows.length; i++) {

            console.log("Business number " + i); 
            var businessName = rows[i].name;
            console.log(businessName);
            console.log();
            var x = rows[i].coordinates.x; 
            var y = rows[i].coordinates.y; 

            getMenuForEachLocation(x, y, businessName, rows, i, function(err, obj) {

                if (err) {
                    callback(err, null); 
                }  

                jsonObject["results"].push(obj);

                if( jsonObject["results"] == the last index) { // figure a way to get last iteration to send back as a response
                     callback(null, jsonObject);  
                 }
            }); 
        }
    }); 
}

retrieveLocation(function(err, jsonObject) {
    if (err) throw err; 

    res.json(jsonObject);
});

An working example of an approach that checks for .length of results array to be equal to .length of rows array. Note, since results is filled asynchronously, the resulting array may not be in order of i

 var rows = [0, 1, 2, 3, 4, 5, 6] , results = [] , asyncFn = function(n) { return new Promise(function(resolve) { setTimeout(function() { resolve(n) }, Math.random() * 3000) }) } , complete = function(callback) { for (var i = 0; i < rows.length; i++) { asyncFn(i).then(function(data) { results.push(data); console.log(results); if (results.length === rows.length) callback(rows, results) }) } } complete( // `callback` : do stuff when `results` `.length` is equal to `rows` `.length` function(rows_, results_) { console.log(rows_, results_) alert("complete"); } ); 

As I understand, the callback function of getCheckinsForEachLocation() only fire if the condition is met, so there is no way you can know when all the data is processed inside the callback function.

We currently know how many rows are with rows.length , and what we need to know is when all the getCheckinsForEachLocation() fired, so we need another index and a oncomplete callback.

This is a working example:

 var globalIndex; // Pseudo async function function getCheckinsForEachLocation (rows, i, callback, oncomplete) { setTimeout(function () { if (-1 != rows[i].indexOf('burgers')) { callback(null, rows[i]); } // Add up the times that the function was called to // find out if they have called all. if (++globalIndex == rows.length) { oncomplete(); } }, Math.random() * 3000); } function retrieveLocation(callback) { // Pseudo data retrived from database var rows = ["sandwich", "burgers 1", "salad", "burgers 2", "sushi", "burgers 3", "tea"]; var jsonObject = { "results" : [] }; // Reset the time that `getCheckinsForEachLocation` was called globalIndex = 0; for (var i = 0, rowsLength = rows.length; i < rowsLength; ++i) { console.log("Business number " + i); getCheckinsForEachLocation(rows, i, function(err, obj) { if (err) { callback(err, null); } jsonObject["results"].push(obj); }, function () { callback(null, jsonObject); }); } } retrieveLocation(function(err, jsonObject) { if (err) throw err; alert(JSON.stringify(jsonObject)); }); 

I mentioned promises earlier - this may work for your needs. I definitely recommend checking out more about promises. Quick note, this is all es6 - so don't get too tied up w/ the arrow function syntax etc. If you're running node 4.0 >= then this should work out of the box;

function retrieveLocation() {
    const locationsWithinVisibleMapRectQuery = "SELECT * FROM locations WHERE Y(coordinates) > " + req.body.SWCoordLat + "AND Y(coordinates) < " + req.body.NECoordLat + "AND X(coordinates) > " + req.body.SWCoordLong + "AND X(coordinates) < " + req.body.NECoordLong + ";";
    return new Promise((resolve, reject) => {
      connection.query(locationsWithinVisibleMapRectQuery, (err, rows) => {
        if (err) reject(err);
        resolve(rows);
      });
    })
    .then(rows => {
      return Promise.all(rows.map(row => {
        const businessName = row.name;
        const x = row.coordinates.x; 
        const y = row.coordinates.y; 
        return new Promise((resolve, reject) => {
          getCheckinsForEachLocation(x, y, businessName, rows, i, (err, result) => {
            if (err) reject (err);
            resolve(result);
          })
        })
        .then(result => result)
        .catch(err => {
          throw new Error(err)
        }); 
      }));
    })
    .then(result => result[result.length - 1]); 
}

retrieveLocation()
  .then(jsonObject => res.json(jsonObject))
  .catch(err => console.log(err));

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