Monday, 24 April 2017

How to cancel NodeJS API and Mongoose query execution?

I am creating an API using MEAN stack which returns GeoJSON for Google Map. GeoJSON is used to draw (multi)polygon on it. The API takes zoom level and bounding box as parameter and based on that the query is fired to get relevant GeoJSON. The API is being called each time when either map get panned or zoomed. I am using bounds_changed event of Google map. Now if there is consecutive API calls, I need to cancel the server and client side call and allow to execute only latest API call. I used timeout for $http and defer() method of $q in angular side to cancel the call (http://stackoverflow.com/a/17328336/1230188) it's working.

For NodeJS I used 'close' event using req's on method. It is also working. But the mongo query is still running for each API call and it uses highest amount of memory and ultimately it hangs the machine.

So I am looking a sure shot way to stop execution of previous API and database calls and allow execute only latest API call.

Node JS API Implementation is given below. I am using waterfall of async framework to get data from three collection.

exports.getZoomBasedShapes = function (req, res) {
    var reqCancelled = false;
    req.on('close', function (err) {
        reqCancelled = true;
       return res.status(499).send({
            message: 'Client Closed Request'
        });
    });

    async.waterfall([
        function(callback) {
            if (req.body.zoomLevel <= 5) {
                State.find({
                    "geometry": { 
                        $geoWithin: {
                           $geometry: {
                              type : "Polygon" ,
                              coordinates: req.body.mapBound
                           }
                        }
                    }
                 }, function (err, lstStates) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null, lstStates);
                    }
                });
            }
            else {
                callback(null, null);
            }
        },
        function(lstStates, callback) {
            if (req.body.zoomLevel <= 18 && req.body.zoomLevel >= 6) {
                County.find({
                    "geometry": { 
                        $geoWithin: {
                           $geometry: {
                              type : "Polygon" ,
                              coordinates: req.body.mapBound
                           }
                        }
                    }
                 }, function (err, lstCounties) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null, lstStates, lstCounties);
                    }
                });
            }
            else {
                callback(null, lstStates, null);
            }
        },
        function(lstStates, lstCounties, callback) {
            if (req.body.zoomLevel <= 21 && req.body.zoomLevel >= 15) {
                Section.find({
                    "geometry": { 
                        $geoWithin: {
                           $geometry: {
                              type : "Polygon" ,
                              coordinates: req.body.mapBound
                           }
                        }
                    }
                 }, function (err, lstSections) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null, lstStates, lstCounties, lstSections);
                    }
                });
            }
            else {
                callback(null, lstStates, lstCounties, null);
            }
        },
        function(lstStates, lstCounties, lstSections, callback) {
            var objFeatureCol = {
                type: "FeatureCollection",
                features: []
            };

            if (lstStates !== null) {
                lstStates.map(function (objState) {
                    objFeatureCol.features.push(objState);
                });
            }

            if (lstCounties !== null) {
                lstCounties.map(function (objCounty) {
                    objFeatureCol.features.push(objCounty);
                });
            }

            if (lstSections !== null) {
                lstSections.map(function (objSection) {
                    objFeatureCol.features.push(objSection);
                });
            }

            callback(null, objFeatureCol);
        }
    ], function (err, result) {
        if (err) {
            res.status(400).send({
                message: errorHandler.getErrorMessage(err)
            });
        }
        else {
            try { 
                if (!reqCancelled) {
                    res.json(result); 
                }
            } catch(e) {};
        }
    });
};

AngularJS Controller and function of API call

(function () {
  'use strict';

  angular
    .module('MyApp')
    .controller('GoogleMapController', GoogleMapController);

  GoogleMapController.$inject = ['$http', 'NgMap', "$q"];

  function GoogleMapController ($http, NgMap, $q) {
    var vm = this;
    vm.canceller = {};

    NgMap.getMap().then(function(map) {
        vm.map = map;
        vm.map.data.setStyle(function(feature) {
            return({
              fillColor: 'transparent',
              strokeWeight: 0.5
            });
        });
        activate();
    });

    vm.mapBoundApiCalled = false;
    vm.mapBoundChanged = function(event) {
        if (vm.map) {
            if (vm.mapBoundApiCalled) {
                vm.canceller.resolve();
            }

            vm.canceller = $q.defer();
            vm.mapBoundApiCalled = true;

            var northEast = vm.map.getBounds().getNorthEast();
            var southWest = vm.map.getBounds().getSouthWest();

            var boundBox = [];
            boundBox.push([northEast.lng(), southWest.lat()]);
            boundBox.push([northEast.lng(), northEast.lat()]);
            boundBox.push([southWest.lng(), northEast.lat()]);
            boundBox.push([southWest.lng(), southWest.lat()]);
            boundBox.push([northEast.lng(), southWest.lat()]);

            $http.post('api/maps/getZoomBasedShapes/', { 
                zoomLevel: vm.map.getZoom(), mapBound: [boundBox] 
            }, { timeout: vm.canceller.promise }).success(function (result) {
                vm.map.data.forEach(function (feature) {
                    vm.map.data.remove(feature);
                });
                vm.map.data.addGeoJson(result);
                vm.mapBoundApiCalled = false;
            }).error(function (data, status, headers, config) {
                console.log(status);
            });
        }
    };

    function activate() {
        vm.canceller = $q.defer();
    }
  }
})();



via Xyroid

No comments:

Post a Comment