vyshkant
@vyshkant
developer

Как заставить javascript дождаться ответа ajax'а?

Здравствуйте!

Уже пару часов бьюсь лбом об стену :)

У меня есть БД с таблицей, в которой хранятся данные о локациях (поля id, location, start, end, description). В колонке location хранится place_id из Google Maps API, по которому эта самая апи может найти локацию и нанести ее на карту (маркером).

По условиям задачи, в течении одного захода на страницу количество записей в таблице может изменяться (записи могут быть изменены, удалены или добавлены). Поэтому было решено, что за создание карты, создание перечня маркеров и за показы этих маркеров будут отвечать три разные функции в javascript'e.

$(document).ready(function()
{
    // создает карту
    var map = create_map();
    
    // создает маркеры, не отображая их
    var markers = create_markers(map);
    
    // показывает, что объекты в массиве есть (об этом читайте далее)
    console.log(markers);
    
    // показывает 0 (об этом читайте далее)
    console.log(markers.length);
    
    // включает отображение маркеров
    setAllMap(map, markers);
});


Собственно, создание карты нехитрое:
function create_map() {
    var mapOptions = {
        center: {lat: 0, lng: 0},
        zoom: 1
    };

    var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
    
    return map;
}


А вот массив, в котором лежат объекты-маркеры получается путем обращения к php-скрипту, который вытягивает и отдает в js массив с данными из базы. Далее этот массив обрабатывается, и на выходе получаем нужные объекты-маркеры.
function create_markers(map) {
    console.log("create_markers_start");
    
    var service = new google.maps.places.PlacesService(map);
    
    var infowindow = new google.maps.InfoWindow();
    
    var markers = [];
    
    $.ajax({
        async:      false,
        url:        '/index.php/locations/get_locations_list_json',
        success:    function(locations_json) {
            console.log("create_markers___ajax_success_start");
                        var locations = $.parseJSON(locations_json);
                        $.each(locations, function(i, location_point) {
                        
                            var request = {
                                placeId: location_point.location
                            };
                            
                            Date.prototype.getMonthName = function() {
                                var month = ['Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'];
                                return month[this.getMonth()];
                            }
                            var desc = location_point.description;
                            var start_obj = new Date(location_point.start);
                            var start = start_obj.getMonthName() + " " + start_obj.getDate();
                            var end_obj = new Date(location_point.end);
                            var end = end_obj.getMonthName() + " " + end_obj.getDate();
                            
                            service.getDetails(request, function(place, status) {
                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    markers[i] = new google.maps.Marker({
                                        map: null,
                                        position: place.geometry.location
                                    });
                                    google.maps.event.addListener(markers[i], 'click', function() {
                                        infowindow.close();

                                        infowindow.setContent('<div><strong>' + place.name + '</strong><br>' +
                                        'You\'re going to visit <u>' + place.formatted_address + '</u>' +
                                        ' <b>from</b> ' + start +
                                        ' <b>to</b> ' + end +
                                        '<br><br>' + desc);
                                        infowindow.open(map, this);
                                    });
                                }
                            });
                        });
                    }
    });
    console.log("create_markers_end");
    return markers;
}


Проблема в следующем: к моменту запуска setAllMap в массиве markers пусто (хотя выше по тексту была функция, которая его должна была заполнить.
Что характерно. Как видите, по тексту расставлены консоль.логи. Ниже показан лог (можно увидеть последовательность выполнения скриптов, а также вывод markers и markers.length:
create_markers_start
create_markers___ajax_success_start
create_markers_end
var_markers
Array[7]
0

В Array[7] можно наблюдать нужные объекты-маркеры. Но, как видите, в это же момент markers.length показывает ноль. Полагаю, это связано с особенностями логирования. Также полагаю, что это связано с тем, что в коде используется вызов ajax'a (который я сделал синхронным, но не помогло).

Как решить проблему? Как сделать, чтобы к моменту запуска функции setAllMap(map, markers) в переменной markers уже был нужный нам массив?

Спасибо.

З.Ы. Для работы всего этого к странице подключена библиотека Google Maps для JS.

---
UPDATE

Спасибо Максим, вопрос с очередностью запуска решено. Но появилась (вернее, не решилась) такая проблема: markers.length по-прежнему равен нулю!
Вот код вызова:
create_markers(map, function(markers) {
        console.log("in `create_markers`: markers: ");
        console.log(markers);
        console.log("in `create_markers`: markers.length: ");
        console.log(markers.length);
    
        // включает отображение маркеров
        setAllMap(map, markers);  
    });

Вот код функции:
function create_markers(map, fn) {
   var markers = {}
   $.ajax(..., {
       success: function(...) {
         ...
         console.log("markers: ");
         console.log(markers);
         console.log("markers.length: ");
         console.log(markers.length);
         fn(markers);
       }
   });
}

А вот консоль:
markers: 
Array[7]
markers.length: 
0
in `create_markers`: markers: 
Array[7]
in `create_markers`: markers.length: 
0


---
UPDATE 2

Проблема решена следующим образом:
function create_markers(map, fn) {
    ...
    var markers_array = [];
    var promises_array = [];
    
    $.ajax({
        ...
        success: function(...)
        {
            $.each(..., function(...) {
                // отслеживаем состояние асинхронного запроса
                var dfd;
                dfd = new $.Deferred();

                // это асинхронный запрос, для его правильной обработки используем промисы
                service.getDetails(..., function(...) {
                    var marker = ...;
                    // по завершению формирования маркера отправляем его в массив
                    markers_array.push(marker);

                    // затем помечаем, что асинхронный запрос завершился успехом
                    dfd.resolve();
                });

                // отправляем отчет об успешном завершении очередного асинхронного запроса в массив с промисами
                promises_array.push(dfd.promise());
            });

            // если все асинхронные вызовы завершились - вызываем callback
            $.when.apply($, promises_array).then(function(){
                fn(markers_array);
            });
        }
    });
}


$(document).ready(function()
{
    ...

    create_markers(map, function(markers_array) {
        ...
    });
});
  • Вопрос задан
  • 12429 просмотров
Решения вопроса 1
zenwalker
@zenwalker
0xABADBABE
Необходимо пробросить в `create_markers` коллбек, который будет вызыван уже после того, как данные загрузятся и объект с маркерами заполнится.

function create_markers(map, fn) {
   var markers = {}
   $.ajax(..., {
       success: function(...) {
         ...
         fn(markers);
       }
   });
}


var map = create_map();
    
create_markers(map, function(markers) {
  // показывает, что объекты в массиве есть (об этом читайте далее)
  console.log(markers);

  // показывает 0 (об этом читайте далее)
  console.log(markers.length);

  // включает отображение маркеров
  setAllMap(map, markers);  
});


В вашем случае объект пустой, по причине того, что возврат из функции пустого объекта происходит раньше, чем будет получен ответ от сервера. Дальше гуглим про асинхронность в js.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
heksen
@heksen
в ajax укажите async: false, но проблема в том что браузер встаёт до тех пор пока не загрузятся данные, т.е. в лучшем случае нужно использовать callbacks.
Ответ написан
Судя по коду, переменная markers определяется два раза:
var markers = create_markers(map);

и затем в create_markers:
var markers = [];

Вот это второе, видимо, лишнее.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы