Ожидание динамически загружаемого скрипта

В моем теле страницы мне нужно вставить этот код в результате вызова AJAX:

    <p>Loading jQuery</p>
    <script type='text/javascript' src='scripts/jquery/core/jquery-1.4.4.js'></script>
    <p>Using jQuery</p>
    <script type='text/javascript'>
        $.ajax({
            ...
        });
    </script>

Я не могу использовать $.load() поскольку документ уже загружен, событие не запускается.

Это безопасно? Если нет, как мне убедиться, что скрипт jquery загружен до того, как будет выполнен мой сгенерированный код.

10 ответов

Решение

Это довольно безопасно. Исторически, <script> теги - полная блокировка, отсюда и вторая <script> тег не может быть найден до того, как первый закончит анализ / исключение. Единственная проблема может заключаться в том, что "современные" браузеры имеют тенденцию загружать скрипты асинхронно и с задержкой. Поэтому, чтобы убедиться, что порядок правильный, используйте его так:

<p>Loading jQuery</p>
<script type='text/javascript' async=false defer=false src='scripts/jquery/core/jquery-1.4.4.js'></script>
<p>Using jQuery</p>
<script type='text/javascript'>
    $.ajax({
        ...
    });
</script>

Тем не менее, вероятно, лучше использовать динамическую вставку тега сценария, а не вставлять это как строку HTML в DOM. Была бы такая же история

var scr  = document.createElement('script'),
    head = document.head || document.getElementsByTagName('head')[0];
    scr.src = 'scripts/jquery/core/jquery-1.4.4.js';
    scr.async = false; // optionally

head.insertBefore(scr, head.firstChild);

Добавьте идентификатор в ваш файл скрипта, чтобы вы могли запросить его.

<script id="hljs" async src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/highlight.min.js"></script>

Затем добавьте слушатель загрузки к нему в JavaScript

<script>
  var script = document.querySelector('#hljs');
  script.addEventListener('load', function() {
    hljs.initHighlightingOnLoad(); 
  });
</script>
const jsScript = document.createElement('script')
jsScript.src =
  'https://coolJavascript.js'

document.body.appendChild(jsScript)

jsScript.addEventListener('load', () => {
  doSomethingNow()
})

Загружается после динамического добавления скрипта

Подождите, пока загрузятся несколько скриптов

Следующий помощник загружает несколько скриптов только один раз и возвращает обещание:

async function cirosantilli_load_scripts(script_urls) {
    function load(script_url) {
        return new Promise(function(resolve, reject) {
            if (cirosantilli_load_scripts.loaded.has(script_url)) {
                resolve();
            } else {
                var script = document.createElement('script');
                script.onload = resolve;
                script.src = script_url
                document.head.appendChild(script);
            }
        });
    }
    var promises = [];
    for (const script_url of script_urls) {
        promises.push(load(script_url));
    }
    await Promise.all(promises);
    for (const script_url of script_urls) {
        cirosantilli_load_scripts.loaded.add(script_url);
    }
}
load_scripts.loaded = new Set();

(async () => {
    await cirosantilli_load_scripts([
        'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js',
        'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js',
    ]);

    // Now do stuff with those scripts.

})();

GitHub upstream: определение и использование.

Протестировано в Chromium 75.

Также есть новая функция в jQuery 1.6, Это называется jQuery.holdReady(), Это на самом деле самоочевидно; когда ты звонишь jQuery.holdReady(true), ready событие не срабатывает, пока вы не позвоните jQuery.holdReady(false), Установка этого значения в false не будет автоматически запускать событие готовности, а просто снимает удержание.

Вот неблокирующий пример загрузки скрипта, взятый из документации:

$.holdReady(true);
$.getScript("myplugin.js", function() {
     $.holdReady(false);
});

Смотрите http://api.jquery.com/jQuery.holdReady/ для получения дополнительной информации

это работает для меня

      function loadScript(sources, callBack) {
    var script      = document.createElement('script');
    script.src      = sources;
    script.async    = false; 
    document.body.appendChild(script); 
    
    script.addEventListener('load', () => {
        if(typeof callBack == "function") callBack(sources);
    });
}
          new MutationObserver((mutationsList, observer) => {
        for (var mutation of mutationsList) 
            if (mutation.type === 'childList') 
                Array.from (mutation.addedNodes)
                    .filter (node => node.tagName === 'SCRIPT')
                    .forEach (script => {
                        //Script started loading
                        script.addEventListener ('load', () => { 
                             //Script finished loading
                        })
                    })
    }).observe(document, { attributes: false, childList: true, subtree: true });

В моем случае решения не сработали. Я хотел программно щелкнуть ссылку после того, как сценарий загрузил событие щелчка правой кнопкой мыши для ссылки. Таким образом, мне пришлось работать с таймаутом и циклом:

      <script>
  var ifrbutton = document.getElementById("id-of-link");
  if(typeof(ifrbutton) != 'undefined' && ifrbutton != null && window.location.hash == "#special-hash") {
    var i = 0;
    // store the interval id to clear in future
    var intr = setInterval(function() {
      if (ifrbutton.onclick!=null) {
        ifrbutton.click();
        clearInterval(intr);
        i = 200;
      }
      if (++i >= 200) clearInterval(intr);
    }, 300)
  }
</script>

Таким образом, решение также может заключаться в том, чтобы через определенные промежутки времени проверять определенные функциональные возможности, которые несет с собой сценарий ... Установите какой-нибудь безопасный конец на тот случай, если сценарий никогда не загрузится ... Работает безопасно для меня;)

Ответ от Чиро Сантилли превосходен. Я немного улучшил его, потому что столкнулся с некоторыми проблемами.

Я сделал специальное поле формы на основе редактора CodeMirror. Так как скрипт для CodeMirror огромен, я загружаю его только по требованию путем инициализации поля.

Если на форме 2 таких поля, то загрузка скрипта запускается дважды просто в один и тот же момент, так как первый запущенный скрипт еще не загружен, то он фактически загружается дважды, что плохо.

Функции MyNamespace.loadScript(script_url) и MyNamespace.loadScripts(script_urls) всегда возвращают Promise, чтобы вы могли решить, использовать ли await или затем.

  1. Если скрипт уже загружен, он возвращает промис, который разрешается немедленно.
  2. Если скрипт загружается, он возвращает обещание с соблюдением интервала, которое разрешается, если скрипт загружен.
  3. Если скрипт никогда не загружался, он возвращает обещание с загрузкой скрипта, который разрешается по событию загрузки.

Код:

      MyNamespace.loadScript = function (script_url) {
    console.log("loading: " + script_url);

    if (!MyNamespace.loadedScripts) MyNamespace.loadedScripts = new Set();
    if (!MyNamespace.loadingScripts) MyNamespace.loadingScripts = new Set();

    if (MyNamespace.loadedScripts.has(script_url)) {
        return new Promise(function (resolve, reject) {
            console.log("already loaded");
            resolve();
        });
    }

    if (MyNamespace.loadingScripts.has(script_url)) {
        return new Promise(function (resolve, reject) {
            console.log("loading in progress");

            let interval = setInterval(function () {
                if (MyNamespace.loadedScripts.has(script_url)) {
                    clearInterval(interval);
                    console.log("waited until loaded");
                    resolve();
                }
            }, 200);
        });
    }

    MyNamespace.loadingScripts.add(script_url);

    return new Promise(function (resolve, reject) {
        let script = document.createElement('script');
        script.src = script_url;

        script.onload = function () {
            console.log("actually loaded");
            MyNamespace.loadedScripts.add(script_url);
            resolve();
        };

        script.onerror = function () {
            console.log("load error");
            reject(new Error("Error by loading the script " + script_url));
        };

        document.head.append(script);
    });
};

MyNamespace.loadScripts = function (script_urls) {
    let promises = [];
    for (let script_url of script_urls) {
        promises.push(MyNamespace.loadScript(script_url));
    }

    return Promise.all(promises);
};

$.ajax и $.load - это одно и то же. Вы можете использовать либо. Если вы поместите $.load в тег скрипта на странице, он сработает при загрузке, как $.ajax().

Когда дело доходит до ожидания запуска сценария, прежде чем делать что-то, то, что сказал Томалак, является своего рода правдой. Исключение составляют асинхронные вызовы. Javascript выполнит вызов AJAX, затем продолжит выполнение скрипта, и когда вызов AJAX ответит, он выполнит успех / неудачу в зависимости от того, что вы скажете.

Теперь предположим, что вы хотите, чтобы ваш JS ожидал возврата, с помощью одного вызова это довольно просто, просто оберните каждую вещь в обратном вызове успеха, или вы можете сделать это классным способом и использовать $.deferred. Это позволяет вам сделать несколько вызовов ajax или один, а затем запустить некоторый код только после финиша.

$.when & $.then являются, вероятно, лучшими в этой ситуации.

В любом случае, то, что вы делаете, безопасно.

Другие вопросы по тегам