JQuery: поиск на основе Ajax - сбой браузера?
Эй, ребята, я работаю над довольно странной моделью поиска на основе AJAX. Поиск на самом деле не извлекает реальные результаты поиска с сервера, а скорее загружает карту сайта (с помощью метода jquery load()) и извлекает ссылки.
Это работает на самом деле очень хорошо, но есть одна маленькая ошибка, которая может привести к сбою моего браузера.
var searchTimer = 0;
$('.s').keyup(function(e) {
switch (e.keyCode) {
//case 8: // Backspace
case 9: // Tab
case 13: // Enter
doSearch(e.keyCode);
break;
case 16: // Shift
...
case 37: // Left
break;
case 38: // Up
doSearch(e.keyCode);
break;
case 39: // Right
break;
case 40: // Down
doSearch(e.keyCode);
break;
...
break;
default:
if (searchTimer != 0) {
clearTimeout(searchTimer);
}
searchTimer = setTimeout(function () {
doSearch(e.keyCode);
}, 250);
}
});
function doSearch(keyCode) {
if ($('.s').val() != '') {
searchTimer = 0;
$sr.load('/sitemap/', function() {
}); // end load
} else {
clearsearch(true);
}
}
Единственная проблема, с которой я столкнулся, это то, что сайт падает, когда я набираю слово в поле ввода.s и сразу же удаляю его в течение 250 мс.
Представьте себе: 1.) вход пуст. 2.) Я быстро набираю "тест". 3.) функция doSearch даже не была запущена, и я нажал cmd-a, чтобы выделить все и удалить текст из ввода.
полный крах моего сайта!
Почему это могло случиться? Это работает действительно гладко и хорошо, когда я просто набираю "test" или удаляю ввод после того, как doSearch() был запущен. Это на самом деле работает всегда. Просто в этом редком случае быстрого набора текста и удаления набранного текста в случае doSeach() происходит сбой.
Есть идеи, что может вызвать это?
edit / update: когда я копирую sitemap.html в мою текущую корневую директорию процессов и загружаю его, он не падает и работает нормально, как в вашем примере. Как только я изменю это на url: "sitemap", dataType: "html",
это падает. Я называю свою карту сайта mydomain.com/sitemap...
код для карты сайта выглядит следующим образом:
<?php
/**
* Template Name: Sitemap
*/
?>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<div id="ajax-base">
<h3>Pages</h3>
<ul>
<?php wp_list_pages('title_li=&depth=0&exclude='); ?>
</ul>
<h3>Posts</h3>
<?php $first = 0;?>
<ul>
<?php
$myposts = get_posts('numberposts=-1&offset=$first');
foreach($myposts as $post) :
?>
<li><a href="<?php the_permalink(); ?>#b"><?php the_title(); ?></a></li>
<?php endforeach; ?>
</ul>
<h3>Categories</h3>
<ul>
<?php wp_list_categories('title_li=&orderby=name'); ?>
</ul>
<h3>Tags</h3>
<ul>
<?php
$tags = get_tags();
foreach ($tags as $tag){
$tag_link = get_tag_link($tag->term_id);
$html .= "<li><a href='{$tag_link}#b' title='{$tag->name} Tag' class='{$tag->slug}'>";
$html .= "{$tag->name}</a></li>";
}
echo $html;
?>
</ul>
</div> <!-- ajax-base -->
<?php endwhile; endif; ?>
извините за этот последний вопрос, но любая идея, почему это имеет значение. Когда я использую эту динамическую / карту сайта в качестве основы для моего поиска, браузер падает. Со статической HTML-страницей все работает нормально.
2 ответа
Я полагаю, что основная проблема вашего кода в том, что вы не прерываете предыдущий ожидающий вызов ajax. Что происходит в браузере, если он одновременно попытается изменить $sr
элемент на два ответа сервера?
Оба старые XMLHttpRequest
и новый jqXHR
имеет метод прерывания, который вы можете использовать.
ОБНОВЛЕНО: Как я описал в комментарии, jQuery.load не намного больше, чем вызов jQuery.ajax и jQuery.html для размещения ответа сервера на странице. Вы можете проверить это, посмотрев в исходный код jQuery.load
здесь (для jQuery 1.4.4) или здесь (для jQuery 1.5.1).
Я подготовил для вас один небольшой демонстрационный пример, который показывает, как вы можете использовать jQuery.ajax и jQuery.html напрямую вместо jQuery.load. Вы можете скачать полный проект здесь.
Если кто-то медленно набирает в поле ввода демо, то получаю следующие результаты
Если один из них печатается быстрее (я набираю очень медленно и поэтому использую тайм-аут на 1 секунду на сервере):
Можно видеть, что я прерываю предыдущий запрос ajax на сервер, если существует какой-либо ожидающий запрос ajax. В случае прерывания error
вызывается соответствующий (предыдущий) запрос ajax, а затем abort()
функция возврата.
Я надеюсь, что если вы последуете пути, у вас никогда не возникнет проблем, которые вы описываете в своем вопросе.
Чтобы быть уверенным, что вы получите пример, я включаю полный код, который я использовал в своей тестовой демонстрации ниже. Код JavaScript следующий
var jqXHR_Old, $myinput = $('#myinput'),
$result = $('#result'), $protocol = $('#protocol'),
logText = function(txt) {
$protocol.append(txt+"<br/>"); // append or prepend
},
doSearch = function(code) {
var txt = $myinput.val();
if (txt != '') {
// send request to the server
if (jqXHR_Old) {
// abort the previous request
logText("aborting...");
jqXHR_Old.abort();
jqXHR_Old = null;
}
$result.empty();
logText('sending request to the server with '+
'<span style="color:blue;">'+txt+'</span>...');
jqXHR_Old = $.ajax({
url: "MySimpleService.svc/GetTestHtmlFragment",
data: {str:txt},
dataType: "html",
success: function (data) {
$result.html(data);
logText("received from the server: "+data);
jqXHR_Old = null;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
if (textStatus !== "abort" || errorThrown !== "abort") {
$result.html("Error Occured!" + " | " + " | " +
textStatus + " | " + errorThrown +
"responseText:<br/>" + XMLHttpRequest.responseText);
} else {
logText("request aborted.");
}
jqXHR_Old = null;
}
});
}
};
$myinput.keyup(function(e) {
switch (e.keyCode) {
//case 8: // Backspace
case 9: // Tab
case 13: // Enter
doSearch(e.keyCode);
break;
case 37: // Left
break;
case 38: // Up
doSearch(e.keyCode);
break;
case 39: // Right
break;
case 40: // Down
doSearch(e.keyCode);
break;
default:
doSearch(e.keyCode);
}
});
HTML здесь
<fieldset style="float:left">
<input type="text" id="myinput"/>
</fieldset>
<div style="clear:left">
<fieldset style="float:left">
<legend>Results from the server:</legend>
<div id="result"></div>
</fieldset>
<div style="clear:left"/>
<fieldset style="float:left">
<legend>Ajax protocol:</legend>
<div id="protocol"></div>
</fieldset>
</div>
В качестве сервера я использую очень простой сервис WCF с интерфейсом
using System.ServiceModel; using System.ServiceModel.Web; using System.ServiceModel.Channels;
namespace AjaxLoad {
[ServiceContract]
public interface ISimpleService {
[OperationContract]
[WebGet]
Message GetTestHtmlFragment (string str);
}
}
и реализация
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading;
namespace AjaxLoad {
[AspNetCompatibilityRequirements (RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class SimpleService : ISimpleService {
public Message GetTestHtmlFragment (string str) {
Thread.Sleep (1000);
return WebOperationContext.Current.CreateTextResponse ("<span style='color:red'>" + str + "</span>",
"text/html; charset=utf-8",
Encoding.UTF8);
}
}
}
Я моделирую медленную обработку запросов только с Thread.Sleep
с 1 сек ожидания. Я использовал SVC-бесплатную реализацию и использовал как web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<standardEndpoints>
<webHttpEndpoint>
<!-- the "" standard endpoint is used by WebServiceHost for auto creating a web endpoint. -->
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" />
</webHttpEndpoint>
</standardEndpoints>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true">
<serviceActivations>
<add relativeAddress="MySimpleService.svc" service="AjaxLoad.SimpleService"
factory="System.ServiceModel.Activation.WebServiceHostFactory" />
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
</configuration>
В качестве "Ссылки" проекта необходимы три зависимые сборки: System, System.ServiceModel, System.ServiceModel.Web.
Попробуй это:
var searchTimer; //define the scope of searchTimer and set it to null
/* ...code...*/
if (searchTimer != null) {
clearTimeout(searchTimer);
}
Идентификатор времени ожидания всегда будет начинаться с 0 и повышаться по мере создания большего количества таймеров.