UndeadDragon
@UndeadDragon
Разработчик ПО на C++, Qt, Python и др.

В чем проблема с асинхронным XMLHttpRequest в Javascript?

Всем привет. Я новичок в JS, поэтому может быть я туплю в чем-то очевидном, но я этого пока не вижу.
Вот в чем дело, мне нужно получить ответы с 4 разных серверов, прежде, чем продолжать выполнение скрипта. Но синхронный запрос не подходит - если сервер упал или не отвечает, то скрипт просто повиснет, потому что синхронному нельзя поставить таймаут.
Я нашел выход в виде создания структуры данных, в которой у каждого запроса есть статус, в начале он STATUS_NONE, и принимающая callback функция начнет выполнение только тогда, когда все запросы будут без статуса STATUS_NONE.
Но приведенный ниже код почему-то не всегда срабатывает. В httpGet все функции получают тот или иной статус, но до места, где комментарий // then function code в createHtmlFromLookup почему-то не всегда доходит. Такое ощущение, что как будто бы вызов с правильными данными перебивается вызовом с данными, где еще не у всех ответов статус отличен от STATUS_NONE.
Буду благодарен, если кто-то поймет, в чем тупняк.
function httpGet(url, data, index, callback, query, options, queries, translations) {
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open("GET", url, true);
  xmlHttp.onload = function (e) {
    if (xmlHttp.readyState == 4) {
      data[index].status = xmlHttp.status;
      callback(xmlHttp.responseText, data[index]);
      createHtmlFromLookup(query, options, data, queries, translations);
    }
  }
  xmlHttp.ontimeout = function (e) {
    data[index].status = xmlHttp.status;
    createHtmlFromLookup(query, options, data, queries, translations);
  }
  xmlHttp.timeout = TIMEOUT;
  xmlHttp.send(null);
}

function createHtmlFromLookup(query, options, trans_data, queries, translations) {
  for (i in trans_data) {
    if (trans_data[i].status == NONE_STATUS) {
      return;
    }
  }
  // then function code
}
  • Вопрос задан
  • 2976 просмотров
Пригласить эксперта
Ответы на вопрос 3
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
Я так понял вам промисы нужны?
Ответ написан
@vdem
Сделайте проще:
1) объявите глобальную переменную-счетчик, со значением, равным количеству запросов, которые нужно сделать,
2) в цикле запускаете все эти запросы, в обработчике (если не было ошибки) делаете декремент счетчика и проверяете его на 0 (если 0 - все ответы получены),
3) можно еще глобальный таймаут сделать, скажем на 10 секунд, если счетчик не 0 - сказать пользователю попробовать еще чуть позже.

P.S. А ошибка как минимум здесь может быть: for (i in trans_data) { - i здесь используется, как будто она глобальная. Ее бы сначала объявить через var в функции.
Ответ написан
Комментировать
zoonman
@zoonman
⋆⋆⋆⋆⋆
Не нужно использовать синхронные запросы, т.к. это существенно замедляет работу веб-приложения и ухудшает отзывчивость интерфейса.
Для работы лучше использовать jQuery, это сэкономит вам время на разработку (а ее размер в 33кб сейчас не очень актуален).
Принцип работы простой.
Вы объявляете 2 переменных, первая хранит информацию о количестве выполненных запросов, а вторая о количестве успешных запросов.
По мере выполнения запросов заполняется массив результатов.
function doit(params, callback) {
	var results = [], // тут мы сохраним результаты 
	finished=0, // сколько запросов мы выполнили всего 
	succefull=0, // сколько запросов было успешных 
	params = params || {};
	var go = function () {
		if (succefull === 4) {
			callback(results);
		} else if (finished === 4) {
			// сделать что-нибудь, если не все запросы были успешны
			// лучший вариант вызывать функцию callback(results, succefull);
		}
	};
	var whenItDone = function (data) {
		results.push(data); // сохраняем ответ от сервера
		finished++;
		succefull++;
		go();
	};
	var whenItFails = function (data) {
		finished++;
		go();
	};
	// выполнить для разных серверов столько раз, сколько надо
	// данный участок можно завернуть в цикл и передавать внутрь имя сервера
	$.get('path/to/server1', params).done(whenItDone).fail(whenItFails);
	...
	$.get('path/to/server4', params).done(whenItDone).fail(whenItFails);

}
// и где-то потом вызвать 
doit(params, function (setOfRecievedData) {
	// здесь вы сможете обработать полученные данные от всех серверов
});
Ответ написан
Комментировать
Ваш ответ на вопрос

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

Похожие вопросы