Загрузка данных через SCRIPT с отловом ошибок
Среди AJAX-транспортов особое место занимает SCRIPT, т.к. запрос данных при помощи динамического скрипта работает на любые домены.
Но как понять - загрузился ли скрипт и выполнился успешно или нет?
Предлагаемое решение предполагает транспорт JSONP, но код определения состояния загрузки скрипта может быть использовано отдельно.
Для отлова окончания загрузки будем использовать onreadystatechange для IE и onload/onerror для остальных браузеров.
var script= document.createElement('script')
script.src = "myUrl.php"
script.onreadystatechange= function () {
if (this.readyState == 'complete' || this.readyState == 'loaded') {
setTimeout(check, 0)
}
}
script.onload = script.onerror = check
document.body.appendChild(script)
В этом коде функция check вызывается при любом окончании загрузки скрипта - успешном или неудачном. А, выполнившись, она сможет проанализировать результат загрузки и понять, что произошло.
Код довольно прост, есть лишь несколько тонких моментов.
Во-первых, используются два readyState .
loaded
- Срабатывает после окончания запроса, в любом случае - загрузился ли скрипт или произошла ошибка (скажем, 404).
complete
- Срабатывает после выполнения скрипта
IE в зависимости от версии генерирует одно или оба readyState в зависимости от своей версии, настроения и результата загрузки.
Подцепившись на оба readyState , я гарантирую, что check вызовется во всех IE, даже если скрипт не загрузился или в нем ошибка. В некоторых версиях и подверсиях IE readyState не работает и придется ловить ошибки по таймауту как и в опере. То есть, таймаут в приложение к описанному способу обязательно должен быть.
Вызов с setTimeout используется, чтобы дать загруженному скрипту квант времени на выполнение. Таким образом, если скрипт загрузился - то check сработает после того, как он выполнится. Как мы увидим далее - это очень важно.
Для остальных браузеров используются события onerror и onload . Конечно, можно было бы разделить обработчики, ведь при вызове onerror - уже понятно, что произошла ошибка.
Следующий код вызывается так:
go('script.php','loaded', 'failed')
Аргументы:
- URL скрипта (можно на другом домене)
- Имя JSONP-каллбэка в случае успешного вызова
- Имя каллбэка при ошибке
Он загрузит скрипт и гарантирует вызов одного или другого каллбэка в зависимости от результата.
При этом учитываются ошибки как при выполнении запроса, так и при выполнении скрипта (мало ли, вдруг там стэктрейс серверного исключения ).
function go(url, callback, errback) {
var complete = false
// временный обработчик
var myCallback = 'callback_'+Math.round(Math.random()*1000000)
window[myCallback] = function(data) {
setComplete()
window[callback](data)
}
url = url+'?callback='+myCallback
/* очищает память */
function setComplete() {
complete = true
try {
// если каллбэк не очистить - утечка памяти захватит все замыкание
delete window[myCallback]
} catch(e) {
window[myCallback] = undefined
}
}
/* эта функция сработает при любом результате запроса */
function check() {
// эта функция запускается так, чтобы при успешной загрузке
// она сработала после каллбэка
if (complete) return
setComplete()
window[errback]()
}
var script = document.createElement('script')
script.onreadystatechange= function () {
if (this.readyState == 'complete' || this.readyState == 'loaded') {
setTimeout(check, 0) // дать скрипту время на выполнение
}
}
script.onload = script.onerror = check
script.src = url
document.body.appendChild(script)
}
В начале кода создается уникальный глобальный callback со случайным именем myCallback . Именно он будет передан как функция для JSONP-вызова.
Функция setComplete вызывается при окончании запроса. Она устанавливает флаг complete и удаляет myCallback .
Дальше все просто:
- Делается запрос JSONP, вызывающий myCallback.
- Если все хорошо - вызовется обработчик, а затем -
check . Обрабочик поставит complete = true , так что check поймет, что делать ей здесь нечего и завершится.
- Если произошла ошибка (любая) - вызовется сразу
check , которая запустит обработчик ошибки.
Этот код был протестирован во всех браузерах и отлично работает везде, включая Firefox, Safari, Chrome, кроме некоторых подверсий IE и Opera.
Так что для Оперы и, заодно, для гарантии работы во всех IE придется использовать старый добрый setTimeout: если за 10 секунд не загрузилось, значит, наверное, что-то стряслось.
Вот демо этого метода: http://javascript.ru/examples/tmp/script.html.
P.S. Этот код написан из интереса к проблеме и является Proof of Concept.
|
Как я понял, то это простой способ загрузить и исполнить JS файл, с проверкой на "серверные" ошибки (если ответ от сервера не 200).
Но вот не менее интересная задача, это отловить ошибки внутри самого загружаемого JS? Например если выполнения скрипта приведет в к ошибке.
У меня есть мысль как реализовать такую проверку, но она ресурсозатратна.
Кстати а в чем проблема Оперы? Я немного по другому такую задачу решил и у меня такой проблемы вроде и не встало.
Работает под IE8, FF, Chrome, Safari, Opera (это под чем я проверял)
Постановка задачи - JSONP-запрос с отловом ошибок (всех возможных). Как вы это реализовали, напишите пожалуйста подробнее ?
Проблема оперы - в том, что она не вызывает ничего при ошибке запроса <script>
Не вполне уяснил для себя необходимость использования динамического callback. Следующий код, вроде как, будет делать тоже самое, но без него.
В чем я не прав? И удалось ли победить Opera? Заранее благодарен за ответ.
проверил: код рабочий, Оперу победил.
Спасибо !
Каллбэк там для JSONP.
"setTimeout(check, 0)"
может, лучше заменить 0 на 1? У меня в каком-то браузере 0 не работал...
Opera - это отвратительный браузер.
ошибка загрузки скрипта отлавливается. Проблема встает в версиях =10.Под любой другой, всегда можно окольным путём найти решения, но только не в Opera.
Самое интересное в данной теме - регресс Oper'ы.
В версиях 9 & <10
-В IE8 complete вызывается при успешной загрузке, только loaded - при ошибке.
Когда то я это проверял.
1.js - Файл с рабочим js
2.js - Файл с синтаксической ошибкой
3.js - Несуществующий файл
IE 5.5
1.js: complete
2.js: complete
3.js: loading
IE 6.0
1.js: complete
2.js: complete
3.js: loading
IE 7.0
1.js: complete
2.js: complete
3.js: complete
IE 7.??
1.js: complete
2.js: complete
3.js: loading
IE 8.0
1.js: complete
2.js: complete
3.js: complete
IE 8.??
1.js: complete
2.js: complete
3.js: loading
Опера 9.63, 2.js не реагирует не на одно из событий, 3.js реагирует на событие onerror.
Опера 10++ не реагирует на события при любой ошибке.
Лучше всех справляются браузеры Chrome 5++ и Safari 3++;
Firefox тоже хорошо справляется, правда если ловить event, при ошибках подкидывает вместо него trace ошибки загрузки скрипта.
В IE радует хотябы что событие вызовется 100%.
Ну а на оперу, как в последнее время бывает, мата не хватит...
Забыл написать, что у любимой мной Opera есть свойство readyState.
Итак, файл 1.js после загрузки поменял состояние на loaded.
Файл 2.js - с синтаксической ошибкой, хоть и не вызвал ни одного обработчика, но принят состояние loaded.
Файл 3.js - отсутствующий, вызвал обработчик onerror в версии 9.63 и не вызавал в 10++, имел состояние interactive (его скрипты в opera преобретают сразу после присоединения к документу)
Для чего я это написал? Может кто и захочет по таймеру отловить загрузку скриптов с синтаксическими ошибками, не вызывающих обработчик))
Удачи.
А что получится, если использовать iframe+ создавать скрипты с async=false?
для Оперы 10.6+ можно использовать Worker + importScript:
код для WebWorker`а (файл wjs.js):
код на странице:
А вот когда произошло обращение к файл (т.е попытка загрузить скрипт) можно как то response status отловить, допустим 302, он ведь по сути не ошибку передает а типа перенаправление, хотелось бы его на нем поимать и не дать перенаправление, а просто вызвать ошибку.