简体   繁体   中英

Node.js Can not push to global array using promises, Mongoose, and GET requests

I can not push the results I need to an array I define in a synchronous function I have created using the Q promise library. There are 3 tasks to this function based on the car make, zip code of user's location, and the maximum radius:

  1. Query my Dealership collection to retrieve dealership id based on the particular manufacturer user has entered.
  2. I then define an array: dealershipIDs. This array will be used to push certain dealership ids to. I then iterate through the returned json list of dealerships to retrieve individual id names of dealerships and their zip codes. I make a GET request to an api service to calculate the distance between users entered location and each dealership found in task 1. If the distance between the dealership and the user is less than the entered radius, that dealership's id name is added to the array that I want to pass to step 3. I am unsuccessful in doing this because the array I try to pass is empty and does not contain the id names outside of the for loop.
  3. Query my Cars collection for the cars that contain the list of dealership ids. This final step would then render the appropriate page with the car results in the user's area.

Task 2 is my problem. I'm able to to add the correct dealership ids to the array I have defined, but I can not pass that array to the next .then because the array is empty outside of the for loop.

I have been stuck on this problem for multiple days now and I have tried everything. Please let me know if I can be more specific.

exports.getCarIndexPage = function(req, res) {
  var m = req.session.make; // User input
  var zipcode = req.session.zipcode; // User input
  var radius = req.session.radius; // User input
  req.session.valid = null; // Resets session variable

  Dealership.find({makes: m}).exec()
    .then(function (ids) {
        var dealershipIDs = []; /* Trying to add dealer ids to array */
        ids.forEach(function (id) {
            var id = ids[i];
            getDistanceWithAPI(zipcode, id.zip, function(distanceVal) {
                console.log("This is the distance: " + distanceVal.distance);
                if (distanceVal.distance <= radius) {
                    console.log("Adding " + id._id + " to array");
                    dealershipIDs.push(id._id); // Adding dealership's id to array
                    console.log("Size of dealership array: " + dealershipIDs.length);
                }   
                console.log("Inside for loop = Size of dealership array: " + dealershipIDs.length); /* Recognizes the array size! */
            })
        })
        console.log("Outside for loop = Size of dealership array: " + dealershipIDs.length); /* Does not recognize array size */
        return dealershipIDs; /* Return this array that contains the dealership ids */
    }).then(
        function (resultArray) {
            Car.find({dealership_id: { $in: resultArray }}).exec()
                .then(function (cars) {
                    console.log(cars);
                }),
                function (error) {
                    console.log("Could not iterate through through cars: " + error);
                }   
    }, function (error) {
        console.error("Error with the outer promises:", error);
    });
}

How can I make this function work by adding to the dealershipIDs array so that I can pass it to be used to query my Cars collection?

The following function is my HTTP request and it returns a JSON object of the distance from point A to point B. ie (distance : 1.664}

function getDistanceWithAPI(userInput, dealerZip, callback) {
https.get('https://www.zipcodeapi.com/rest/xApFwnm4tosuL2gX2UDQIGcknN2NIHyfhXVNlhRPFkjrmzpou2edJry7fAVXhtdz/distance.json/' 
        + userInput + '/' + dealerZip + '/mile', function(res) {
  var body = ''; // Will contain the final response

  res.on('data', function(data){
    body += data;
  });

  // After the response is completed, parse it and log it to the console
  res.on('end', function() {
    var parsed = JSON.parse(body);
    callback(parsed); // i.e. returns {distance : 1.664 } 
  });
})

// If any error has occured, log error to console
.on('error', function(e) {
  console.log("Got error: " + e.message);
});
}

Here is my log:

Server running at http://localhost:3000/
Outside for loop = Size of dealership array: 0
[]
This is the distance: 1.664
Adding bmwofsf to array
Size of dealership array: 1
Inside for loop = Size of dealership array: 1
This is the distance: 13.685
Adding bmwofsanrafael to array
Size of dealership array: 2
Inside for loop = Size of dealership array: 2

The problem I guess is because in 2nd task, getDistanceWithAPI is an async function. Therefore, 2nd task will quickly return before any getDistanceWithAPI resolve. Let me try to use preso code to fix your issue below. it is not perfect, because it introduced a global array, may be we can improve it by playing with Q.all a little bit.

var dealershipIDs = []; /* put it outside, because the results in 2nd tasks is used to indicated the finished state.  */
Dealership.find({makes: m}).exec()
    .then(function (ids) {
        var promises = []
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i];
            promises.push(getDistanceWithAPI(zipcode, id.zip, function(distanceVal) { // Returns promise
                console.log("This is the distance: " + distanceVal.distance);
                if (distanceVal.distance <= radius) {
                    console.log("Adding " + id._id + " to array");
                    dealershipIDs.push(id._id); // Adding dealership's id to array
                    console.log("Size of dealership array: " + dealershipIDs.length);
                }   
                console.log("Inside for loop = Size of dealership array: " + dealershipIDs.length); /* Recognizes the array size! */
            }));
        }
        console.log("Outside for loop = Size of dealership array: " + dealershipIDs.length); /* Does not recognize array size */
        return Q.all(promises); // resolve all promises and return;
    }).then(
        function () {
            var resultArray = dealershipIDs;
            Car.find({dealership_id: { $in: resultArray }}).exec()
                .then(function (cars) {
                    console.log(cars);
                }),
                function (error) {
                    console.log("Could not iterate through through cars: " + error);
                }   
    }, function (error) {
        console.error("Error with the outer promises:", error);
    });
exports.getCarIndexPage = function(req, res) {
var m = req.session.make;
var zipcode = req.session.zipcode;
var radius = req.session.radius;
req.session.valid = null; // Resets session variable

Dealership.find({makes: m}).exec()
    .then(function (ids) {
        var promises = [];
        ids.forEach(function (id) {
            /* Pushing ascynchrounous functions into promise array */
            promises.push(getDistanceWithQPromise(zipcode, id.zip, id._id));
        });
        return Q.all(promises)
            .then(function (promise) {
                var dealershipIDs = []; /* Adding dealership ids to array */
                promise.forEach(function (promiseData) {
                    var distance = promiseData.distance;
                    var id = promiseData.id;
                    if (distance <= radius) {
                        console.log("Adding " + id + " to array");
                        dealershipIDs.push(id); // Adding dealership's id to array
                    }
                });
                console.log("Outside for loop = Size of dealership array: " + dealershipIDs.length); /* Does recognize array size */
                return dealershipIDs;
            }, function (err) {
                console.log(err)
            });
    }).then(function (resultArray) { // Receives the dealership Id array
            Car.find({dealership_id: { $in: resultArray }}).exec()
                .then(function (cars) {
                    renderResult(res, req, cars);
                }),
                function (error) {
                    console.log("Could not iterate through through cars: " + error);
                }   
    }, function (error) {
        console.error("Error with the outer promises:", error);
    });
}

Had to modify my GET request so that it returns a promise using the Q library. I also had to add the dealership ID to the returned response so that I can access it in getCarIndexPage as a promise value.

function getDistanceWithQPromise(userInput, dealerZip, dealerID) {
var deferred = Q.defer();
var request = https.request('https://www.zipcodeapi.com/rest/xApFwnm4tosuL2gX2UDQIGcknN2NIHyfhXVNlhRPFkjrmzpou2edJry7fAVXhtdz/distance.json/' 
        + userInput + '/' + dealerZip + '/mile', function(response) {
    var responseData = '';
    response.on('data', function (data) {
        responseData += data;
    });

    /* Adding the dealer ID to the response string so that I can convert it to JSON before returning the promise */ 
    response.on('end', function() {
        responseData = responseData.slice(0, -1);
        responseData += "," + '"id":' + '"'+dealerID+'"' + "}";

        deferred.resolve(JSON.parse(responseData));
    });

    });

    request.on('error', function(err) {
            deferred.reject(err);
    });

    request.end();
    return deferred.promise;
};

Huge shoutout to Ron for suggesting adding the asynchronous calls to a promise array and using Q.all. That was exactly what I needed.

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