 
			
				18.06.2009, 18:53
			
			
			
		  
	 | 
 
	
		
		
		
			
			| 
			
				
				
				 Интересующийся 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 18.06.2009 
					
					
					
						Сообщений: 11
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	| 
	
	
		
		
			
			 
				IE6 повисает при закрытии окна в котором выполняется асинхронная загрузка данных
			 
			
		
		
		
		Проблема проявляется в IE6, в FireFox 3.0.10 не воспроизводится. 
Сут проблемы в следующем. На страничке есть динамический контент, который подгружается асинхронно средствами XMLHttpRequest. Эта страничка открывается в новом окне по ссылке с другой странички, собственно, если закрыть это самое новое окно пока XMLHttRequest не вернет данные и повторить это несколько раз то после определнного момента, страница перестает открываться в этом самом новом окне.
 
Собственно нужно понять почему, и заставить страницу работать и не повисать в IE6 в указанном выше сценарии. (т.е. что бы можно было открывать ее сколько угодно раз кликая по ссылке и закрывать новое окно не заботясь загрузились ли динамически данные до конца или нет)
 
Теперь более подробнее то что мне удалось выяснить, здесь и прошу вашей помощи, может кто-то сталкивался или имеет достаточно знаний в JavaScript под IE что бы посоветовать куда копать дальше.
 
Мои тесты показали, что для того, что бы страница начала зависать при открытии в новом окне достаточно закрытием окна броузера оборвать динамическую загрузку данных 2 раза. После этого функционал динамической загрузки данных (т.е. все что связано с XMLHttpRequest) перестает работать и при открытии страничек в новом окне по упомянутой ссылки и собственно на исходной родительской странице тоже все связанное с XMLHttpRequest перестает работать.
 
После этого предположил, что экземпляр XMLHttpRequest, а в IE это на самом деле объект DOM, остается повисшим в памяти, что и приводит к указанной проблеме. Переделал код на использования подхода с проверкой свойства readyState по таймеру вместо установки свойства onreadystatechanged (подход обсуждался здесь на форуме, также упомянут в источнике  http://xmlhttprequest.ru/#problem). 
 
Проверил страницу на наличие утечек памяти с помощью JSLeaksDetector - утечек не обнаружил. (ссылки по теме утечек памяти в IE: 
 http://blogs.msdn.com/gpde/pages/jav...-detector.aspx,
 http://www.codeproject.com/KB/script...kpatterns.aspx)
 
Собственно здесь я пришел в тупик. Заранее спасибо за любые мнения и советы!
 
Ниже код класса, который я написал для выполнения асинхронных запросов, он по идее должен поддерживать выполнения нескольких одновременных асинхронных запросов. (в IE, если не ошибаюсь, существует ограничение в 2 запроса, ну у меня собственно 2 асинхронных запроса и используются). И соответсвенно из кроссброузерности волнует только IE и FireFox, поэтому класс может быть получился и не универсальным. Замечания к классу тоже привествуются.:
 !!! Внимание, ниже представлен откорректированный код, который содержит исправление описанной проблемы, исправления выделены красным, подробности см. в следующем посте.
	
 
	| 
		 Код: 
	 | 
 
	//////////////////////////////////////////////////////////////////////////////////////////////////////
// Asynchronous caller class (uses AJAX technique).
// It avoids cases that lead memory leaks in IE6 as it doesn't set onreadystatechange property.
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor.
//  completeCallback - callback that will be executed when request is completed
//  state - (optional parameter) state parameter that will be supplied as 4-th argument of completeCallback function when it is called
function AsyncCaller(completeCallback, state)
{
	if (!completeCallback)
		throw "Callback is not specified for AsyncCaller constructor";
		
	var httpReq = this.GetHttpRequest();
	if (!httpReq)
		throw "Unable to create an instance of XMLHttpRequest.";
	this.httpReq = httpReq;
	this.completeCallback = completeCallback;
	this.state = state;
	this.pollInterval = 10;	// ms.
	
	// Ensure async call is aborted when document is unloaded
	var self = this;
	this.OnWindowUnload = function() { self.AbortExecution(); }
	AddEvent(window, "unload", this.OnWindowUnload);
}
// Returns a new initialized instance of XMLHttpRequest class
AsyncCaller.prototype.GetHttpRequest = function()
{
	var result = null;
	if (window.XMLHttpRequest)
	{
		// Mozilla, Safari,
		result = new XMLHttpRequest();
		if (result.overrideMimeType)
		{
			result.overrideMimeType('text/xml');
		}
	}
	else if (window.ActiveXObject)
	{
		// IE
		try
		{
			result = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
			try
			{
				result = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e)
			{}
		}
	}
	return result;
}
// Call GET or POST method asynchronously. When it is completed callback passed to constructor will be executed
AsyncCaller.prototype.Call = function(uri, postRequest, headers)
{
	var method, body;
	
	if (postRequest)
	{
		method = "POST";
		body = postRequest;
	}
	else
	{
		method = "GET";
		body = null;
	}
	
	this.httpReq.open(method, uri, true);
	this.httpReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); 
	this.httpReq.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
	this.httpReq.setRequestHeader("Cache-Control", "no-cache");
	
	if (headers)
	{
		for (var i = 0; i < headers.length; i++)
		{
			if (headers[i] && headers[i] != "")
			{
				var colonIndex = headers[i].indexOf(":");
				if (colonIndex != -1)
				{
					var headerName = headers[i].substring(0, colonIndex);
					var headerValue = headers[i].substring(colonIndex + 1);
					this.httpReq.setRequestHeader(headerName, headerValue);
				}
			}
		}
	}
	
	this.StartTimer();
	this.httpReq.send(body);
}
// Internal callback that is called with the interval defined by property 'pollInterval'
// untill request is completed
AsyncCaller.prototype.OnReadyStateChange = function()
{
	if (this.httpReq.readyState == 4)
	{
		this.StopTimer();
		RemoveEvent(window, "unload", this.OnWindowUnload);
		this.completeCallback(this.httpReq.status, this.httpReq.statusText, this.httpReq.responseText, this.state);
	}
	else
	{
		var self = this;
		this.timerID = setTimeout(
			function()
			{
				self.OnReadyStateChange();
			},
			this.pollInterval);
	}
}
// Start timer (pooling of the state of ajax response)
AsyncCaller.prototype.StartTimer = function()
{
	this.StopTimer();
	var self = this;
	this.timerID = setTimeout(
		function()
		{
			self.OnReadyStateChange();
		},
		this.pollInterval);
}
// Stop timer
AsyncCaller.prototype.StopTimer = function()
{
	if (this.timerID)
	{
		clearTimeout(this.timerID);
		this.timerID = null;
	}
}
// Aborts execution of async request
AsyncCaller.prototype.AbortExecution = function()
{
	this.StopTimer();
	this.httpReq.abort();
}
// Register event for the element
function AddEvent(elem, type, handler){
	if (elem.addEventListener){
		// DOM compliant browsers (Mozilla, FF, ...)
		elem.addEventListener(type, handler, false)
	}
	else
	{
		// IE
		elem.attachEvent("on"+type, handler)
	}
}
// Remove event for the element
function RemoveEvent(elem, type, handler){
	if (elem.removeEventListener){
		// DOM compliant browsers (Mozilla, FF, ...)
		elem.removeEventListener(type, handler, false)
	}
	else
	{
		// IE
		elem.detachEvent("on"+type, handler)
	}
}
 | 
 
	
 
 
 
		
	
		
		
		
		
		
		
		
						  
				
				Последний раз редактировалось prike, 10.11.2009 в 17:23.
				Причина: Добавил исправления проблемы в оригинальный код
				
			
		
		
	
		
		
	
	
	 | 
 
 
	 
		 | 
 
 
	
	
	
		
	
		
		
		
			
			 
			
				19.06.2009, 14:38
			
			
			
		  
	 | 
 
	
		
		
		
			
			| 
			
				
				
				 Интересующийся 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 18.06.2009 
					
					
					
						Сообщений: 11
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	
	
	
		
		
		
		
		В общем, я нашел способ как устранить проблему - необходимо принудительно обрывать асинхронную загрузку данных методом abort при выгрузке документа. 
Видимо если этого не делать в IE6 объект XMLHttpRequest остается висеть в состоянии ожидания данных, т.о. если набирается два объекта в таком состоянии, то вероятно по известному ограничению IE ("в 2 одновременных соединения к одному домену" - взято из  http://xmlhttprequest.ru/xhr#problem)  следующий объект XMLHttpRequest не может загрузить данные, и весь функционал связанный с асинхронной передачей данных отваливается как на родительской странице так и на всех дочерних (если я не ошибаюсь то они все работают в одном потоке, видимо это и накладывает ограничение). 
 
В общем может в деталях есть некоторые неточности, но общий смысл думаю понятен. Исправленный код не стал выставлять в этом посте, что бы не дублировать его, вместо этого подправил оригинальный код в первом сообщении темы (изменения выделены красным).
 
Надеюсь кому-нибудь пригодится эта информация, кто наступит на те же грабли и сможет исправить ее быстрее. У меня же ушло около 3-х дней что бы понять в чем причина   , т.к. в интернете упоминания проблемы именно в таком ключе не нашел.
 
Комментарии приветсвуются, заранее спасибо!  
		
	
		
		
		
		
		
		
	
		
			
			
	
			
			
			
			
			
				 
			
			
			
			
			
			
				
			
			
			
		 
		
	
	
	 | 
 
 
	 
		 | 
 
 
	
	
	
		
	
		
		
		
			
			 
			
				05.03.2010, 15:17
			
			
			
		  
	 | 
 
	
		
		
		
			
			| 
			
				
				
				 Интересующийся 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 18.06.2009 
					
					
					
						Сообщений: 11
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	
	
	
		
		
		
		
		Спустя более чем пол года в приложении для которого писался класс AsyncCaller появились некоторые проблемы с производительностью на очень больших для браузера объемах данных, в связи с чем мне пришлось заняться профилированием производительности и памяти (для профилирования памяти использовал JavaScript Memory Validator -  http://www.softwareverify.com/javasc...ory/index.html). В результате чего так же удалось обнаружить изъян в представленном выше классе AsyncCaller. 
 
Проблема в том, что в методе OnReadyStateChange (который вызывается через каждые 10 миллисекунд и проверяет не пришел ли респонс) в случае если респонс еще не пришел/не загружен до конца в в метод установки таймера передается безымянная функция, котора служит только для того, что бы через 10 миллисекунд опять вызвать OnReadyStateChange. В безымянную функцию передается единственный параметр self через замыкание, для того что бы вызвать OnReadyStateChange нужного объекта. Вот этот код:
 
	
 
	| 
		 Код: 
	 | 
 
	
if (this.httpReq.readyState == 4)
	{
		this.StopTimer();
		RemoveEvent(window, "unload", this.OnWindowUnload);
		this.completeCallback(this.httpReq.status, this.httpReq.statusText, this.httpReq.responseText, this.state);
	}
	else
	{
		var self = this;
		this.timerID = setTimeout(
			function()
			{
				self.OnReadyStateChange();
			},
			this.pollInterval);
	}
 | 
 
	
 
 
Сама суть проблемы в том, что если этот код выполняется часто (а например при 10 секундном ожидании респонса  он выполнится 1000 раз), то он приводит к созданию большого количества объектов функций, которые конечно будут подобраны сборщиком мусора, но до того как он сработает, они расходуют лишнюю память.
 
Что бы избежать этого я переписал класс, отказавшись от замыкания.  Кроме того увеличил poolInterval до 100, т.е. метод OnReadyStateChange (который проверяет пришел ли респонс) будет вызываться 10 раз в секунду (тогда как в оригинальной версии вызывался 100 раз). Переписанный код класса ниже, ключевые изменения выделены красным.
 
В качестве PS. cразу оговорюсь по коду - AsyncCaller.handles это объект, читай хештаблица, а не массив, потому что он гарантированно будет разреженным (кроме того класс может использоваться одновременно для посылки нескольких запросов поэтому пустота будет не только в начале массива, но и заполненная часть так же может иметь пропуски, хотя их скорее всего их будет 1 - 2, т.к. одновременный запуск десятков асинхронных запросов не оправдан, а в IE 6 их вообще нельзя запустить более 2 одновременно). Хотя здесь можно было бы использовать и массив JavaScript, так как они тоже должны хорошо работать с разреженными массивами, это скорее уже дело вкуса. Сам я реализацию с массивом на производительность не тестировал.
 
	
 
	| 
		 Код: 
	 | 
 
	// Constructor. 
//  completeCallback - callback that will be executed when request is completed
//  state - (optional parameter) state parameter that will be supplied as 4-th argument of completeCallback function when it is called
function AsyncCaller(completeCallback, state)
{
	if (!completeCallback)
		throw "Callback is not specified for AsyncCaller constructor";
		
	var httpReq = this.GetHttpRequest();
	if (!httpReq)
		throw "Unable to create an instance of XMLHttpRequest.";
	this.httpReq = httpReq;
	this.completeCallback = completeCallback;
	this.state = state;
	this.pollInterval = 100;	// ms.
	
	var handle = AsyncCaller.handleCount++;
	this.handle = handle;
	AsyncCaller.handles[handle] = this;
	
	// Ensure async call is aborted when document is unloaded
	var self = this;
	this.OnWindowUnload = function() { self.AbortExecution(); }
	AddEvent(window, "unload", this.OnWindowUnload);
}
// Object handles to references map (we use an instance of Object as a Dictionary)
AsyncCaller.handles = {};
AsyncCaller.handleCount = 0;
// Returns a new initialized instance of XMLHttpRequest class
AsyncCaller.prototype.GetHttpRequest = function()
{
	var result = null;
	if (window.XMLHttpRequest)
	{
		// Mozilla, Safari,
		result = new XMLHttpRequest();
		if (result.overrideMimeType)
		{
			result.overrideMimeType('text/xml');
		}
	}
	else if (window.ActiveXObject)
	{
		// IE
		try
		{
			result = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
			try
			{
				result = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e)
			{}
		}
	}
	return result;
}
// Call GET or POST method asynchronously. When it is completed callback passed to constructor will be executed
AsyncCaller.prototype.Call = function(uri, postRequest, headers)
{
	if (this.timerID != null)
	{
		throw "This method cannot be called untill the Ajax request initiated by previous call of this method is not completed.";
	}
	var method, body;
	
	if (postRequest)
	{
		method = "POST";
		body = postRequest;
	}
	else
	{
		method = "GET";
		body = null;
	}
	
	this.httpReq.open(method, uri, true);
	this.httpReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); 
	this.httpReq.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
	this.httpReq.setRequestHeader("Cache-Control", "no-cache");
	
	if (headers)
	{
		for (var i = 0; i < headers.length; i++)
		{
			if (headers[i] && headers[i] != "")
			{
				var colonIndex = headers[i].indexOf(":");
				if (colonIndex != -1)
				{
					var headerName = headers[i].substring(0, colonIndex);
					var headerValue = headers[i].substring(colonIndex + 1);
					this.httpReq.setRequestHeader(headerName, headerValue);
				}
			}
		}
	}
	
	this.StartTimer();
	this.httpReq.send(body);
}
// Internal callback that is called with the interval defined by property 'pollInterval'
// untill request is completed
function OnReadyStateChange(callerHandle)
{
	var instance = Handle2Reference(callerHandle);
	
	if (instance != null)
	{
		if (instance.httpReq.readyState == 4)
		{
			instance.StopTimer();
			RemoveEvent(window, "unload", instance.OnWindowUnload);
			ReleaseHandle(instance.handle);
			instance.completeCallback(instance.httpReq.status, instance.httpReq.statusText, instance.httpReq.responseText, instance.state);
		}
		else
			instance.timerID = setTimeout("OnReadyStateChange(" + instance.handle + ")", instance.pollInterval);
	}
}
// Returns a reference to an object by its handle
function Handle2Reference(handle)
{
	if (AsyncCaller.handles.hasOwnProperty(handle))
		return AsyncCaller.handles[handle];
	return null;
}
// Release handle
function ReleaseHandle(handle)
{
	if (AsyncCaller.handles.hasOwnProperty(handle))
	{
		AsyncCaller.handles[handle] = null;
		delete AsyncCaller.handles[handle];
	}
}
AsyncCaller.prototype.StartTimer = function()
{
	this.StopTimer();
	this.timerID = setTimeout("OnReadyStateChange(" + this.handle + ")", this.pollInterval);
}
AsyncCaller.prototype.StopTimer = function()
{
	if (this.timerID != null)
	{
		clearTimeout(this.timerID);
		this.timerID = null;
	}
}
// Aborts execution of async request
AsyncCaller.prototype.AbortExecution = function()
{
	this.StopTimer();
	this.httpReq.abort();
	
	ReleaseHandle(this.handle);
} | 
 
	
 
 
 
		
	
		
		
		
		
		
		
		
						  
				
				Последний раз редактировалось prike, 10.03.2010 в 17:26.
				
				
			
		
		
	
		
		
	
	
	 | 
 
 
	 
		 | 
 
 
	
	
	
		
	
		
		
		
			
			 
			
				05.03.2010, 15:32
			
			
			
		  
	 | 
 
	
		
		
		
			
			| 
			
				
				
				 Интересующийся 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 18.06.2009 
					
					
					
						Сообщений: 11
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	| 
	
	
		
		
		
		
		 Если кто-то знает как этот функционал реализован в jQuery поделитесь пожалуйста, что бы сделать тему законеченной, на сколько я знаю там должен быть реализован подобный подход, но могу и путать, самому разбираться сейчас к сожалению времени нет совершенно. 
		
	
		
		
		
		
		
		
	
		
		
	
	
	 | 
 
 
	 
		 | 
 
 
	
	
	
		
	
		
		
		
			
			 
			
				05.03.2010, 15:49
			
			
			
		  
	 | 
 
	
		
		
		
			  | 
			
			
				
				
				 Профессор 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 03.04.2009 
					
					
					
						Сообщений: 1,263
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	
	
	
		
		
		
		
		В jQuery 1.3.2 так: 
setInterval(onreadystatechange, 13);
 
В jQuery 1.4.2 отказались от такой проверки и вешают функцию на onreadystatechange. Почитайте код метода $.ajax -- там все понятно.  
		
	
		
		
		
		
		
		
	
		
		
	
	
	 | 
 
 
	 
		 | 
 
 
	
	
	
		
	
		
		
		
			
			 
			
				03.12.2011, 09:02
			
			
			
		  
	 | 
 
	
		
		
		
			  | 
			
			
				
				
				 Новичок на форуме 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 02.12.2011 
					
					
					
						Сообщений: 1
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	| 
	
	
		
		
			
			 
				Даже не знаю
			 
			
		
		
		
		Давно эта тема не поднималась)  
Смотрю народ оживился 
		
	
		
		
		
		
		
		
	
		
		
	
	
	 | 
 
 
	 
		 | 
 
 
	
	
	
		
	
		
		
		
			
			 
			
				03.12.2011, 09:09
			
			
			
		  
	 | 
 
	
		
		
		
			
			| 
			
				
				
				 что-то знаю 
				
				
				
				
	
 
 
 
			 | 
			  | 
			
				
				
					Регистрация: 24.05.2009 
					
					
					
						Сообщений: 5,176
					 
					
					
			
		
 
		 
		
			 | 
		 
		 
		
	 | 
 
	| 
	
	
		
		
		
		
		 Что значит оживился? Я например уже более года не поддерживаю ИЕ6 поэтому и тему нет смысла поднимать, ИЕ6 прошлый век и его место в прошлом. Да и ИЕ7 тоже по хорошему забросил бы поддерживать, если бы она не была предустановлена в винВиста по дефолту. 
		
	
		
		
		
		
		
		
	
		
		
	
	
	 | 
 
 
	 
		 | 
 
 
 
 |  
  |