Javascript.RU

Загрузка данных через 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')

Аргументы:

  1. URL скрипта (можно на другом домене)
  2. Имя JSONP-каллбэка в случае успешного вызова
  3. Имя каллбэка при ошибке

Он загрузит скрипт и гарантирует вызов одного или другого каллбэка в зависимости от результата.

При этом учитываются ошибки как при выполнении запроса, так и при выполнении скрипта (мало ли, вдруг там стэктрейс серверного исключения ).

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.

Дальше все просто:

  1. Делается запрос JSONP, вызывающий myCallback.
  2. Если все хорошо - вызовется обработчик, а затем - check. Обрабочик поставит complete = true, так что check поймет, что делать ей здесь нечего и завершится.
  3. Если произошла ошибка (любая) - вызовется сразу check, которая запустит обработчик ошибки.

Этот код был протестирован во всех браузерах и отлично работает везде, включая Firefox, Safari, Chrome, кроме некоторых подверсий IE и Opera.
Так что для Оперы и, заодно, для гарантии работы во всех IE придется использовать старый добрый setTimeout: если за 10 секунд не загрузилось, значит, наверное, что-то стряслось.

Вот демо этого метода: http://javascript.ru/examples/tmp/script.html.

P.S. Этот код написан из интереса к проблеме и является Proof of Concept.

+3

Автор: goldserg, дата: 2 августа, 2010 - 13:40
#permalink

Как я понял, то это простой способ загрузить и исполнить JS файл, с проверкой на "серверные" ошибки (если ответ от сервера не 200).

Но вот не менее интересная задача, это отловить ошибки внутри самого загружаемого JS? Например если выполнения скрипта приведет в к ошибке.
У меня есть мысль как реализовать такую проверку, но она ресурсозатратна.

Кстати а в чем проблема Оперы? Я немного по другому такую задачу решил и у меня такой проблемы вроде и не встало.
Работает под IE8, FF, Chrome, Safari, Opera (это под чем я проверял)


Автор: Илья Кантор, дата: 2 августа, 2010 - 17:19
#permalink

Постановка задачи - JSONP-запрос с отловом ошибок (всех возможных). Как вы это реализовали, напишите пожалуйста подробнее ?

Проблема оперы - в том, что она не вызывает ничего при ошибке запроса <script>


Автор: javs, дата: 21 августа, 2010 - 12:52
#permalink

Не вполне уяснил для себя необходимость использования динамического callback. Следующий код, вроде как, будет делать тоже самое, но без него.

var loaded=false;

function go(url) {

   loaded=false;

   var script = document.createElement('script');

   function check() {
     alert(loaded?'LOADED':'FILED'); 
     document.body.removeChild(script); return true;
   }

   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)
}

В чем я не прав? И удалось ли победить Opera? Заранее благодарен за ответ.


Автор: Samuelle (не зарегистрирован), дата: 26 августа, 2010 - 11:38
#permalink

проверил: код рабочий, Оперу победил.

Спасибо !


Автор: Илья Кантор, дата: 6 декабря, 2010 - 18:24
#permalink

Каллбэк там для JSONP.


Автор: Яростный Меч, дата: 26 августа, 2010 - 11:15
#permalink

"setTimeout(check, 0)"

может, лучше заменить 0 на 1? У меня в каком-то браузере 0 не работал...


Автор: Гость (не зарегистрирован), дата: 10 сентября, 2010 - 11:02
#permalink

Opera - это отвратительный браузер.
Под любой другой, всегда можно окольным путём найти решения, но только не в Opera.
Самое интересное в данной теме - регресс Oper'ы.
В версиях 9 & <10

ошибка загрузки скрипта отлавливается. Проблема встает в версиях =10.

Автор: W-Supremacy (не зарегистрирован), дата: 10 сентября, 2010 - 14:39
#permalink

-В 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%.

Ну а на оперу, как в последнее время бывает, мата не хватит...


Автор: W-Supremacy (не зарегистрирован), дата: 10 сентября, 2010 - 15:05
#permalink

Забыл написать, что у любимой мной Opera есть свойство readyState.
Итак, файл 1.js после загрузки поменял состояние на loaded.
Файл 2.js - с синтаксической ошибкой, хоть и не вызвал ни одного обработчика, но принят состояние loaded.
Файл 3.js - отсутствующий, вызвал обработчик onerror в версии 9.63 и не вызавал в 10++, имел состояние interactive (его скрипты в opera преобретают сразу после присоединения к документу)

Для чего я это написал? Может кто и захочет по таймеру отловить загрузку скриптов с синтаксическими ошибками, не вызывающих обработчик))
Удачи.


Автор: Гость (не зарегистрирован), дата: 4 мая, 2011 - 10:40
#permalink

Да тупее "осла" браузера не сыскать


Автор: 4esn0k (не зарегистрирован), дата: 9 мая, 2011 - 17:31
#permalink

А что получится, если использовать iframe+ создавать скрипты с async=false?


Автор: Yaffle (не зарегистрирован), дата: 22 мая, 2011 - 20:44
#permalink

для Оперы 10.6+ можно использовать Worker + importScript:
код для WebWorker`а (файл wjs.js):

self.onmessage = function (event) {
  var url = event.data,
    jsonp = '__jsonp' + (Math.random() * 1000000).toFixed(0),
    data = '';
  self[jsonp] = function (x) {
    data = JSON.stringify(x);
  };
  try {
    importScripts(url.replace('{callback}', jsonp));
  } catch (e) {
  // на случай, как ошибок , связанных с загрузкой, так и со скриптом
}
  postMessage(data);
  close();//?
};

код на странице:

function getJSON(url, callback, onerror) {
      var worker = new Worker('wjs.js');
      worker.onmessage = function (event) {
        if (event.data === '' && onerror) {
          onerror();
        } else {
          callback(JSON.parse(event.data));
        }
      };
      worker.postMessage(url);
    };

Автор: Гость (не зарегистрирован), дата: 21 января, 2013 - 10:28
#permalink

А вот когда произошло обращение к файл (т.е попытка загрузить скрипт) можно как то response status отловить, допустим 302, он ведь по сути не ошибку передает а типа перенаправление, хотелось бы его на нем поимать и не дать перенаправление, а просто вызвать ошибку.


Автор: Гость (не зарегистрирован), дата: 16 апреля, 2022 - 01:18
#permalink

Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
2 + 5 =
Введите результат. Например, для 1+3, введите 4.
 
Поиск по сайту
Другие записи этого автора
Илья Кантор
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Популярные таги
Последние комментарии
Последние темы на форуме
Forum