Узкие места в асинхронный код JavaScript на карте Google


Я работаю на выбоину карта веб-приложение в Google приложений скрипт. На карте получает данные из таблицы Google (с ручной съемке), который затем преобразуется через просмотров геолокации и карты Google дорог Апис. Есть 101 маркеров на карте (один маркер на строку данных, которая представляет собой выбоину или кластера выбоин). Эта карта, по некоторым странным причинам, занимает около 20 секунд, чтобы поначалу нагрузки. Недопустимо!

Это приложение позволяет использовать следующие API:

  • в jQuery
  • Google Карты Дорог
  • Гугл Геолокация
  • async.js
  • Mustache.js

Я профилированного мой код , и от того, что он, кажется, большую часть времени процессор от методов, которые не появляются, чтобы быть бизнеса. Я проанализировал профиль код двумя способами:

Само время

Существует по крайней мере три тяжелых вызовов функций в этом плане, хотя ничего такого, что вроде бы тоже неплохо. enter image description here

Общее время

Нет никакой бизнес-логики на самом верху этого списка, но много ниже его. Таким образом, я представляю: enter image description here

Потом я определить строки кода, где эти проблемы появляются:

addPotholeMarker()

/* Adds marker to map.
 * Parameters : 
 *  • potholeData  : a PotholeData (or PotholeDataFromCoords) object
 *  • snappedToRoad: boolean
 *  • callback     : the function to call next (optional)
 * Returns : 
 *  • the marker that was added to the map, or null if arguments invalid, or callback() if it was provided
 */
function addPotholeMarker(potholeData, snappedToRoad, callback) {
  // make sure that callback is either falsy or a function
  if ((callback) && (typeof callback !== 'function')) throw new TypeError('callback specified but not a function. Thrown from addPotholeMarker()'); 
  // make sure potholeState is either falsy or contains iconURL string
  if ((!potholeData.potholeState) || ((potholeData.potholeState) && (potholeData.potholeState.iconURL === undefined))) throw new Error('invalid potholeData');
  // let's make sure to snap this to road if it isn't already...  
  var coords = new GPSCoordinates(potholeData.lat, potholeData.lng);
  if (!snappedToRoad) 
  { 
    var potholeMarker = 'a garbage return value';
    getRoadCoordinates(coords).done(function(response) {
        var coords = response.snappedPoints[0].location;
        potholeData.lat = coords.latitude;
        potholeData.lng = coords.longitude;
        potholeData.isSnappedToRoad(true);
        return (potholeMarker = addPotholeMarker(potholeData, true, callback));
   /*     potholeMarker = addPotholeMarker(potholeData, true);
        return potholeMarker;*/
    });
    if (callback) return callback(null);
    return; 
    //return potholeMarker;
  }
  var marker = new google.maps.Marker({
    position: coords,
    title: coords.toString(),
    map: map,
    opacity: 0.5,
    icon: ((potholeData.potholeState.iconURL !== undefined) ? potholeData.potholeState.iconURL : PURPLE_MARKER)
  });

  // make marker have effect when mouseout,mouseover
  marker.addListener('mouseover', function(mouseEvent) {
    marker.setOpacity(1.0);
  });
  marker.addListener('mouseout', function(mouseEvent) {
    marker.setOpacity(0.5);

  });

  var infoWindow = createInfoWindow(potholeData);
  // save infoWindow for later reference
  infoWindows[statesMap.get(getPotholeStateFor(potholeData))].push(infoWindow);
  // on click of marker, show infoWindow
  marker.addListener('click', function(mouseEvent) { 
    infoWindow.open(map, marker);
  });
  // add this to potholeMarkers
  potholeMarkers[statesMap.get(getPotholeStateFor(potholeData))].push(marker);  
  if (callback) return callback(null, marker);
  return marker;
}

snapPotholeCoordsToRoad()

/* snaps potholes stored in potholeCoordinates to road
 * Parameters: 
 *  • potholeCollection : (Object<Array<PotholeData> >) the Collection of PotholeData to use
 *  • callback          : the function to call next
 */
// TODO: refactor the body of this so as to use potholeCollection (preferrably instead of potholeCoordinates)
function snapPotholeCoordsToRoad(potholeCollection, callback)
{
    var DEBUG = false;
    // guarantee that callback is function
    if ((callback) && (typeof(callback) !== 'function')) throw new TypeError('callback is something, but not a function. Thrown from snapPotholeCoordsToRoad().');
    // for each element of potholeCollection
    if (DEBUG) console.log('potholeCollection === ' + JSON.stringify(potholeCollection, null, '\t'));
    var keys = [];
    for (var key in potholeCollection)
    {
        if (typeof potholeCollection[key] !== 'function') keys.push(key);
    }
    for (var key in potholeCollection)
    {
        (function itr(k, m) { 
            if (typeof potholeCollection[k] === 'function') return;
            if (m === potholeCollection[k].length) return;
            if (DEBUG) console.log('potholeCollection.' + k + '[' + m + '] == ' + JSON.stringify(potholeCollection[k][m], null, '\t'));
            // if element (PotholeData) not snapped to road
            if (!potholeCollection[k][m].isSnappedToRoad())
            {
                // get road coordinates for element
                getRoadCoordinates(potholeCollection[k][m])
                // replace element's coordinates with those road coordinates
                .done(function(newCoords) { 
                    potholeCollection[k][m].setCoordinates(newCoords.snappedPoints[0].location);
                    //debugger;
                    potholeCollection[k][m].isSnappedToRoad(true);
                    if (DEBUG) console.log('potholeCollection.' + k + '[' + m + '] == ' + JSON.stringify(potholeCollection[k][m], null, '\t'));
                    if ((k === keys[keys.length - 1]) && (m === potholeCollection[k].length - 1) && (callback)) return callback(null, null, potholeCollection);
                    itr(k, m+1);
                })
            }
            else
            {
                if ((k === keys[keys.length - 1]) && (m === potholeCollection[k].length - 1) && (callback)) return callback(null, null,  potholeCollection);
                itr(k, m+1);
            }
        })(key, 0);
    }
}

addPotholeMarkers()

/* put all potholes on the map 
 * Parameters:
 *  • callback         : the function to call next
 *  • unsnappedPotholes: (<Object<Array<PotholeData> > ) collection of potholes with coordinates that have not been snapped to road
 *  • snappedPotholes  : (<Object<Array<PotholeData> > ) collection of potholes with coordinates that are snapped to road
 */
function addPotholeMarkers(unsnappedPotholes, snappedPotholes, callback)
{
    var DEBUG = false;
    // guarantee that callback is function
    if ((callback) && (typeof(callback) !== 'function')) throw new TypeError('callback is something, but not a function. Thrown from addPotholeMarkers().');
    // add all the markers for them to the map
    async.waterfall([
        function(cb) { 
            async.eachOf(unsnappedPotholes, function(value, key) {
                // TODO: refactor this
                async.eachOf(value, function (v, k) { 
                    if (DEBUG) console.log('dealing with unsnappedPotholes');
                    //console.assert(v.potholeState.toString() != '[object Object]', 'toString() override missing');
                    //v = revivePotholeState(v); // unnecessary, because addPotholeMarker() invokes getPotholeStateFor() to force the potholeState to be in the statesMap
                    addPotholeMarker(v, false);
                })
            })
            cb(null);
        }, function(cb) {
            async.eachOf(snappedPotholes, function(value, key) { 
                async.eachSeries(value, function(pothole, fn) { 
                    if (DEBUG) console.log('pothole == ' + JSON.stringify(pothole, null, '\t'));
                    addPotholeMarker(pothole, 
                        true,
                        //pothole.isSnappedToRoad(),
                        fn); 
                })
            })
            cb(null);
        }], function(err, results) {
            console.log('trying to center map');

            adjustMap();
            console.log('Map recentered');
            if (callback) { 
                callback(err);
            }
        });


}

Функции водителя

google.script.run.withSuccessHandler(function(data) { 
        async.waterfall([//fetchServerPotholeData,  // for some reason, this function is not receiving data
            function(callback) { 
                fetchServerPotholeData(data, callback);
            },
            fetchCoords,
            snapPotholeCoordsToRoad,
            addPotholeMarkers
            ], function(err, result) { 
                if (!err) { 
                    console.log('Everything successfully done. Enjoy your map!'); 

            }
            else 
            {
                console.error(err);
            }
        }
    )
}).withFailureHandler(console.log).getPotholeData();

Я пытаюсь исправить эти проблемы, но я не уверен, как.

Обновление

Я добавляю ссылку на гитхаб проект , который я создал другой день, так как этот вопрос был сверчков, так как я попросил его.

Вот HTML код в приложение (возможно, скрипт тратит много времени загрузки необходимого <script></code> tags): </p> <pre><code><!DOCTYPE html> <html> <head> <base target="_top"> <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places,geocoding,geometry&key=AIzaSyCcjfkPZ0EfqZnyJrOZ3cuqdAWTFlXmFxM&callback=initMap" async defer></script> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.0/mustache.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/async@2.5.0/dist/async.min.js"></script> <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css"> <!--<script src="https://google-developers.appspot.com/_static/js/jquery-bundle.js"></script>--> <script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <?!= include('mathFunctions'); ?> <?!=// include('Pothole'); ?> <?!= include('JavaScript'); ?> <?!= include('stylesheet'); ?> </head> <body> <div id="toggleHidden" class="floatingMenu">&mdash;</div> <div id="legend" class="floatingMenu"> <span class="center row title">Legend</span> <!-- the different icons and states of potholes --> <div> </div> </div> <div id="map"> </div> </body> </html>



Комментарии
1 ответ

Хранить координаты в скрипт приложений Google, а не получать каждый раз на стороне клиента

Как я говорил в своем комментарии, да получение GPS-координат могут быть обработаны на стороне сервера.

Функция getPotholesFromDataStore (или другой способ) можно использовать геокодер услуги. Затем используйте лист класс для записи значений в таблице.

function getPotholesFromDataStore()
{
//doesn't change, so use const
const POTHOLE_SPREADSHEET = 'https://docs.google.com/spreadsheets/d/1gxDeZUSykyEtL4B7WUYLeKqkDJpuc1uF02Jp_p2lfOg/edit?usp=sharing';

//new variable for using spreadsheet for reading and then writing later
var spreadsheet = SpreadsheetApp.openByUrl(POTHOLE_SPREADSHEET);
//since we opened the spreadsheet above, substitute that here
var dataStore = spreadsheet.getDataRange().getValues();
//use this for writing later - take the first sheet (i.e. Sheet 1)
var sheet = spreadsheet.getSheets()[0];

//make a geocoder object and set the boundaries for Indianapolis - adjust accordingly
var boundGeocoder = Maps.newGeocoder()
// The latitudes and longitudes of southwest and northeast corners of Indianapolis, respectively
.setBounds(39.635765, -86.466493, 40.042790, -85.915804);

for (var j = 1; j < dataStore.length; j++)
{
// check for latitude,longitude of the pothole on the current row, using the PotholeDataHelper
var latLngPresent = PotholeData.isValidCoord(dataStore[j][helperA.argIndices[0]], true) &&
PotholeData.isValidCoord(dataStore[j][helperA.argIndices[1]], false);

if (dataStore[j][3]) { //address available
var response = boundGeocoder.geocode(dataStore[j][3]);
if (response.results.length) {
var result = response.results[0]; //take the first result
var rng = sheet.getRange(j+1, 2, 1, 2);
var values = rng.getValues();
var valuesToSet = [[result.geometry.location.lat, result.geometry.location.lng]];
rng.setValues(valuesToSet);
}

}

в jQuery

Поскольку у вас есть 5 библиотек JS, я бы вопрос, Является ли jQuery-это действительно необходимо (и предлагаю вам посмотреть youmightnotneedjQuery.com если вы еще не). В выборки по API или один из трех предложенных на YMNNJQ возможно заменить на jQuery JavaScript-кодом (т. е. $.ajax()).

Я вижу места, в jQuery код, как $('#legend div:first').append(). Альтернативы использованию, что в jQuery псевдо селектор можно было бы добавить атрибут id, что первый <div> элемента и через что document.getElementById()или выберите его с документа.querySelector() и затем с помощью метода appendChild() , чтобы добавить HTML-код.

const и let

Я определенно рекомендовал бы заменить var С let и const где это уместно в рамках .GS файлов и если браузер совместимости - это вопрос, используют его в обычный JavaScript-файлы, а также.

Обратите внимание, в примере выше, я использовал const POTHOLE_SPREADSHEET - это потому, что это значение является постоянным и не должен быть повторно назначены.

const POTHOLE_SPREADSHEET = 'https://docs.google.com/spreadsheets/d/1gxDeZUSykyEtL4B7WUYLeKqkDJpuc1uF02Jp_p2lfOg/edit?usp=sharing';

1
ответ дан 22 февраля 2018 в 05:02 Источник Поделиться