Javascript включает в себя не загрузку позже на странице

У нас есть приложение Rails, в котором мы включаем зависимости приложений в html-заголовке в application.js:

//= require jquery
//= require analytics
// other stuff...

Затем на отдельных страницах в нижней части страницы есть тег script для analytics:

<script>
  analytics.track('on that awesome page');
</script>

Это нормально работает, но очень редко мы видим ошибку analytics is not defined, совсем недавно в Chrome 43. Поскольку все должно быть загружено синхронно, похоже, что это должно работать из коробки, но я изменил script to:

<script>
  $(document).ready(function () {
    analytics.track('on that awesome page');
  });
</script>

А теперь вместо этого каждый раз мы видим $ is not defined. Мы не видим никаких других ошибок от одного и того же IP-адреса, иначе я бы предположил, что что-то пошло не так в application.js. Любые другие идеи, почему это может сломаться? Вы можете увидеть пример страницы здесь.

Полный application.js:

// Polyfills
//= require es5-shim/es5-shim
//= require es5-shim/es5-sham
//= require polyfills
//
// Third party plugins
//= require isMobile/isMobile
//= require jquery
//
//= require jquery.ui.autocomplete
//= require jquery.ui.dialog
//= require jquery.ui.draggable
//= require jquery.ui.droppable
//= require jquery.ui.effect-fade
//= require jquery.ui.effect-slide
//= require jquery.ui.resizable
//= require jquery.ui.tooltip
//
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backbone-sortable-collection
//= require bootstrap
//= require load-image
//= require react
//= require react_ujs
//= require classnames
//= require routie
//= require mathjs
//= require moment
//= require stink-bomb
//= require analytics
//
// Our code
//= require_self
//= require extensions
//= require extend
//= require models
//= require collections
//= require constants
//= require templates
//= require mixins
//= require helpers
//= require singletons
//= require actions
//
//= require object
//= require components
//= require form_filler
//= require campaigns
//= require form_requests
//= require group_wizard
//= require step_adder

Chalk = {};
underscore = _;

_.templateSettings = {
  evaluate:    /\{\{(.+?)\}\}/g,
  interpolate: /\{\{=(.+?)\}\}/g,
  escape:      /\{\{-(.+?)\}\}/g
};

moment.locale('en', {
  calendar: {
    lastDay: '[Yesterday at] LT',
    sameDay: '[Today at] LT',
    nextDay: '[Tomorrow at] LT',
    lastWeek: 'dddd [at] LT',
    nextWeek: '[Next] dddd [at] LT',
    sameElse: 'L LT'
  }
});

Update:

Мы все еще видим это на производстве изредка. Мы также видели это в случае, когда мы загружаем script до application.js, а затем ссылаемся на него:

javascript_include_tag 'mathjs'
javascript_include_tag 'application'

Каждый так часто мы видим ошибку math is not defined. Мне интересно, произошла ли ошибка при загрузке mathjs или других скриптов, которые не позволяют загружать ее, но тот факт, что это происходит во многих разных библиотеках и поэтому редко, кажется менее вероятным. Мы проверили некоторые отладочные проверки, чтобы узнать, полностью ли загружена наша application.js, и она часто не кажется, даже если вы получаете доступ к чему-то вроде JQuery позже на странице.

Одна из причин заключалась в том, чтобы избежать уведомления старых браузеров о запущенных сценариях слишком долго, но мы можем просто отказаться от них и потянуть все это на application.js, чтобы избежать ошибок.

Ответ 1

Мы по-прежнему видим эту ошибку несколько раз в день и не получили окончательного ответа на то, что вызывает ее. Мое лучшее предположение заключается в том, что либо предыдущие сценарии тайм-аут загрузки, либо пользователь быстро перемещается между ссылками, что предотвращает загрузку страницы полностью. Они также могут просто использовать кнопку "Назад", вызывающую странное поведение в сценариях на странице.

Ответ 2

Это может произойти, если вы не дождитесь загрузки script, который определяет analytics, или если вы не определяете порядок загрузки файлов javascript. Убедитесь, что script, который определяет analytics, всегда загружается, прежде чем пытаться вызвать его метод track. В зависимости от вашей установки скрипты могут загружаться в случайном порядке, что приводит к непредсказуемому поведению.

Вы пытались убедиться, что все загружено, но слушатель $(document).ready(function () {}); просто гарантирует, что DOM готов, а не доступна аналитика. И здесь у вас такая же проблема. $ - это просто jQuery, поэтому $ is not defined означает, что jQuery еще не загружен. Вероятно, ваш script появился до загрузки jQuery и попытался вызвать то, что еще не определено.

Ответ 3

Основа вашей проблемы, вероятно, заключается в вашем предположении:

все должно быть загружено синхронно

Все решительно не загружается синхронно. Протокол HTTP 1.1 поддерживает piplining и из-за закодированное кодирование передачи ваши объекты, на которые ссылаются, могут или не могут выполнить загрузку до того, как ваша основная веб-страница завершит загрузку.

Все это происходит асинхронно, и вы не можете гарантировать порядок их загрузки. Вот почему браузеры делают несколько параллельных подключений к вашему веб-серверу при загрузке одной веб-страницы. Поскольку javascript и jQuery управляются событиями, они по своей природе асинхронны которые могут запутаться, если вы не хорошо понимаете это поведение.

Сочетание вашей проблемы заключается в том, что документ загружает JavaScript-событие (помните, jQuery только расширяет JavaScript) вызывается, когда DOM готов, который может быть до изображений и другого внешнего содержимого. И да, этот внешний контент может включать вашу ссылку на jquery.js script. Если это загрузится после DOM, вы увидите ошибку" $не определена". Поскольку связанный script еще не загружен, селектор jQuery undefined. Аналогично, с вашими другими связанными библиотеками.

Попробуйте вместо этого использовать $(window).load(). Это должно работать, когда все ссылочные объекты и загружены DOM.

Ответ 4

Поскольку сценарии имеют тенденцию загружать произвольные заказы, вы можете заставить свою аналитику script загрузить после того, как все будет готово.

Существуют различные подходы к этой задаче.

Скалы HTML5 дали довольно хороший фрагмент для такого рода вещей. Кроме того, в зависимости от ваших потребностей вы можете использовать загрузчик модуля, например require.js. Использование загрузки promises и Require.js довольно сладкое тоже. Как правило, вы используете много JavaScript, загрузчик модулей поможет вам в этом беспорядке.

Я сохранил самый уродливый и наименее эффективный, еще один надежный подход к концу. Ожидание загрузки внешней библиотеки может создавать огромные узкие места, и их следует рассматривать и тщательно обрабатывать. Вывод аналитики script составляет async и очень сложно обрабатывать. В этих случаях я предпочитаю

Загрузка внешних отложенных сценариев как-то просто:

function loadExtScript(src, test, callback) {
  var s = document.createElement('script');
  s.src = src;
  document.body.appendChild(s);

  var callbackTimer = setInterval(function() {
    var call = false;
    try {
      call = test.call();
    } catch (e) {}

    if (call) {
      clearInterval(callbackTimer);
      callback.call();
    }
  }, 100);
}

Взгляните на этот пример, когда я загружаю jQuery динамически с помощью функции обещания при загрузке script: http://jsfiddle.net/4mtyu/487/

Вот демонстрационная загрузка цепочки Angular и jQuery в порядке: http://jsfiddle.net/4mtyu/488/

Учитывая приведенный выше пример, вы можете загрузить свою аналитику как:

loadExtScript('https://code.jquery.com/jquery-2.1.4.js', function () {
    return (typeof jQuery == 'function');
}, jQueryLoaded);

function jQueryLoaded() {
    loadExtScript('https://analytics-lib.com/script.js', function () {
        return (typeof jQuery == 'function');
    }, analyticsLoadedToo);
}

function analyticsLoadedToo() {
    //cool we are up and running
    console.log("ready");
}

Ответ 5

Этот код загрузит каждый URL script, помещенный в libs в точном порядке, ожидая полной загрузки до добавления следующего. Он не так оптимизирован, как позволяет браузеру делать это, но позволяет отслеживать ошибки и принудительно выполнять порядок загрузки.

(function(){
    var libs = [
        "http://example.com/jquery.js",
        "http://example.com/tracker.js",
        "http://example.com/myscript.js"
    ];
    var handle_error = function() {
        console.log("unable to load:", this.src);
    };
    var load_next_lib = function() {
        if(libs.length) {
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.src = libs.shift();
            script.addEventListener("load", load_next_lib);
            script.addEventListener("error", handle_error);
            document.body.appendChild(script);
        }
    };
    load_next_lib();
})();

Но я бы посоветовал вам проверить каждый тег <script> вашего сайта и посмотреть, есть ли у них атрибут defer="" или async="". Большинство проблем исходят из них, потому что они говорят браузеру выполнить script позже. Они также могут быть в неправильном порядке.

Ответ 6

Как описывали другие, вы загружаете скрипты, а затем пытаетесь выполнить код до того, как ваши скрипты завершили загрузку. Поскольку это временная прерывистая ошибка вокруг относительно небольшого количества кода, расположенного в вашем HTML, вы можете исправить ее с помощью простого опроса:

(function runAnalytics () {
  if (typeof analytics === 'undefined') {
    return setTimeout(runAnalytics, 100);
  }
  analytics.track('on that awesome page');
}());

... то же самое можно использовать и для других библиотек, надеюсь, вы увидите шаблон:

(function runJQuery () {
  if (typeof $ === 'undefined') {
    return setTimeout(runJQuery, 100);
  }
  $(document).ready(...);
}());

Изменить: Как отмечалось в комментариях, события load лучше подходят для этого. Проблема в том, что эти события могут быть уже запущены к моменту выполнения script, и вам придется написать резервный код для этих сценариев. Чтобы не писать 10 строк кода для выполнения 1 строки, я предложил простой, неинвазивный, быстрый и грязный, обязательно работая над каждым решением опроса браузера. Но чтобы не получить больше downvotes, здесь более правильное утомительное и сложное решение, если у вас есть говядина с опросом:

<script>
// you can reuse this function
function waitForScript (src, callback) {
  var scripts = document.getElementsByTagName('script');
  for(var i = 0, l = scripts.length; i < l; i++) {
    if (scripts[i].src.indexOf(src) > -1) {
      break;
    }
  }
  if (i < l) {
    scripts[i].onload = callback;
  }
}

function runAnalytics () {
  analytics.track('on that awesome page');
}

if (typeof analytics === 'undefined') {
  waitForScript('analytics.js', runAnalytics);
} else {
  runAnalytics();
}

function runJQuery () {
  $(document).ready(...);
}

if (typeof $ === 'undefined') {
  waitForScript('jquery.min.js', runJQuery);
} else {
  runJQuery();
}
</script>

Ответ 7

У этого есть классное решение. Правильно загрузите js для вашего приложения. Здесь, сначала загружаю jQuery, а затем загружаю analytics.js. Надеюсь, это решит вашу проблему для поддерживаемых html5 браузеров. Код:

    var fileList =[
                    'your_file_path/jQuery.js',
                    'your_file_path/analytics.js'
                  ];

    fileList.forEach(function(src) {
    var script = document.createElement('script');
    script.src = src;
    script.async = false;
    document.head.appendChild(script);
 });

Этот фрагмент кода сначала загрузит jQuery, а затем загрузит файл analytics.js. Надеюсь, это решит вашу проблему.