Подтвердить что ты не робот

Динамическая загрузка JavaScript синхронно

Я использую шаблон модуля, одна из вещей, которую я хочу сделать, - это динамически включать внешний файл JavaScript, выполнять файл, а затем используйте функции/переменные в файле в return { } моего модуля.

Я не могу понять, как это сделать легко. Существуют ли стандартные способы выполнения псевдосинхронной внешней нагрузки script?

function myModule() {
    var tag = document.createElement("script");
    tag.type = "text/javascript";
    tag.src = "http://some/script.js";
    document.getElementsByTagName('head')[0].appendChild(tag);

    //something should go here to ensure file is loaded before return is executed

    return {
        external: externalVariable 
    }
}
4b9b3361

Ответ 1

Существует только один способ синхронной загрузки и выполнения ресурса script, который использует синхронный XHR

Это пример того, как это сделать

// get some kind of XMLHttpRequest
var xhrObj = createXMLHTTPObject();
// open and send a synchronous request
xhrObj.open('GET', "script.js", false);
xhrObj.send('');
// add the returned content to a newly created script tag
var se = document.createElement('script');
se.type = "text/javascript";
se.text = xhrObj.responseText;
document.getElementsByTagName('head')[0].appendChild(se);

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

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

Ответ 2

Ответ соответствует НЕ.

Загрузка файла синхронно не совпадает с выполнением файла синхронно - это то, что запросил OP.

Принятый ответ загружает синхронизацию файлов, но не более чем добавляет тег script в DOM. Просто потому, что appendChild() вернулся, в любом случае не гарантирует, что script завершил выполнение, и его члены инициализируются для использования.

Единственный способ (см. оговорку) для решения вопроса OP - это синхронизировать загрузку script поверх XHR, как указано, затем читать как текст и передавать либо в eval(), либо в новый вызов Function(), и ждать этого для возврата. Это единственный способ гарантировать загрузку script И синхронно.

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

Протест: Если вы не используете веб-работников, в этом случае просто вызовите loadScripts();

Ответ 3

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

Utilities.require = function (file, callback) {
    callback = callback ||
    function () {};
    var filenode;
    var jsfile_extension = /(.js)$/i;
    var cssfile_extension = /(.css)$/i;

    if (jsfile_extension.test(file)) {
        filenode = document.createElement('script');
        filenode.src = file;
        // IE
        filenode.onreadystatechange = function () {
            if (filenode.readyState === 'loaded' || filenode.readyState === 'complete') {
                filenode.onreadystatechange = null;
                callback();
            }
        };
        // others
        filenode.onload = function () {
            callback();
        };
        document.head.appendChild(filenode);
    } else if (cssfile_extension.test(file)) {
        filenode = document.createElement('link');
        filenode.rel = 'stylesheet';
        filenode.type = 'text/css';
        filenode.href = file;
        document.head.appendChild(filenode);
        callback();
    } else {
        console.log("Unknown file type to load.")
    }
};

Utilities.requireFiles = function () {
    var index = 0;
    return function (files, callback) {
        index += 1;
        Utilities.require(files[index - 1], callBackCounter);

        function callBackCounter() {
            if (index === files.length) {
                index = 0;
                callback();
            } else {
                Utilities.requireFiles(files, callback);
            }
        };
    };
}();

И эти утилиты могут использоваться

Utilities.requireFiles(["url1", "url2",....], function(){
    //Call the init function in the loaded file.
    })

Ответ 4

Самая Node.js-подобная реализация, с которой я мог придумать, могла синхронизировать файлы JS и использовать их как объекты/модули

var scriptCache = [];
var paths = [];
function Import(path)
{
    var index = 0;
    if((index = paths.indexOf(path)) != -1) //If we already imported this module
    {
        return scriptCache [index];
    }

    var request, script, source;
    var fullPath = window.location.protocol + '//' + window.location.host + '/' + path;

    request = new XMLHttpRequest();
    request.open('GET', fullPath, false);
    request.send();

    source = request.responseText;

    var module = (function concealedEval() {
        eval(source);
        return exports;
    })();

    scriptCache.push(module);
    paths.push(path);

    return module;
}

Пример источника (addobjects.js):

function AddTwoObjects(a, b)
{
    return a + b;
}

this.exports = AddTwoObjects;

И используйте его следующим образом:

var AddTwoObjects = Import('addobjects.js');
alert(AddTwoObjects(3, 4)); //7
//or even like this:
alert(Import('addobjects.js')(3, 4)); //7

Ответ 5

У меня была следующая проблема с существующими ответами на этот вопрос (и варианты этого вопроса в других потоках stackoverflow):

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

Или, чуть точнее:

  • Ни один из загружаемого кода не был отлаживаемым (за исключением блока тегов HTML script, тогда и только тогда, когда решение добавило элементы script в дом) и никогда не было как отдельными видимыми сценариями.) = > Учитывая, как многие сценарии, которые я должен загрузить (и отлаживать), это было неприемлемо.
  • Решения, использующие события onreadystatechange или onload, не удалось заблокировать, что было большой проблемой, поскольку исходный код первоначально загружал динамические скрипты, используя "require ([filename, 'dojo/domReady']); и я удалял dojo.

Мое окончательное решение, которое загружает script перед возвратом, И имеет все скрипты, правильно доступные в отладчике (по крайней мере для Chrome), выглядит следующим образом:

ПРЕДУПРЕЖДЕНИЕ: Следующий код должен использоваться только в режиме разработки. (для режима "release" я рекомендую предварительную упаковку и минимизацию БЕЗ динамической загрузки script или, по крайней мере, без eval).

//Code User TODO: you must create and set your own 'noEval' variable

require = function require(inFileName)
{
    var aRequest
        ,aScript
        ,aScriptSource
        ;

    //setup the full relative filename
    inFileName = 
        window.location.protocol + '//'
        + window.location.host + '/'
        + inFileName;

    //synchronously get the code
    aRequest = new XMLHttpRequest();
    aRequest.open('GET', inFileName, false);
    aRequest.send();

    //set the returned script text while adding special comment to auto include in debugger source listing:
    aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';

    if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
    {
        //create a dom element to hold the code
        aScript = document.createElement('script');
        aScript.type = 'text/javascript';

        //set the script tag text, including the debugger id at the end!!
        aScript.text = aScriptSource;

        //append the code to the dom
        document.getElementsByTagName('body')[0].appendChild(aScript);
    }
    else
    {
        eval(aScriptSource);
    }
};

Ответ 6

var xhrObj = new XMLHttpRequest();
xhrObj.open('GET', '/filename.js', false);
xhrObj.send(null);
eval(xhrObj.responseText);

Если это междоменный запрос, он не будет работать. В этом случае вам нужно загрузить запрошенный файл на ваш сервер или сделать зеркальный php, который выводит его, и потребовать, чтобы php.

С jquery (работает также с междоменным запросом):

$.getScript('/filename.js',callbackFunction);

callbackFunction будет вызываться синхронно.

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

Ответ 7

Если вам нужно загрузить произвольное количество скриптов и продолжать работу только после последнего, и вы не можете использовать XHR (например, из-за ограничений CORS), вы можете сделать следующее. Он не является синхронным, но позволяет выполнять обратный вызов точно при загрузке последнего файла:

// Load <script> elements for all uris
// Invoke the whenDone callback function after the last URI has loaded
function loadScripts(uris,whenDone){
  if (!uris.length) whenDone && whenDone();
  else{
    for (var wait=[],i=uris.length;i--;){
      var tag  = document.createElement('script');
      tag.type = 'text/javascript';
      tag.src  = uris[i];
      if (whenDone){
        wait.push(tag)
        tag.onload = maybeDone; 
        tag.onreadystatechange = maybeDone; // For IE8-
      }
      document.body.appendChild(tag);
    }
  }
  function maybeDone(){
    if (this.readyState===undefined || this.readyState==='complete'){
      // Pull the tags out based on the actual element in case IE ever
      // intermingles the onload and onreadystatechange handlers for the same
      // script block before notifying for another one.
      for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1);
      if (!wait.length) whenDone();
    }
  }
}

Изменить. Обновлено для работы с IE7, IE8 и IE9 (в режиме quirks). Эти версии IE не запускают событие onload, но делают это для onreadystatechange. IE9 в режиме стандартов запускает оба (с onreadystatechange для всех сценариев, запускаемых перед onload для любого).

На основе этой страницы может быть небольшая вероятность, что старые версии IE никогда не отправят событие onreadystatechange с readyState=='complete'; если это так (я не смог воспроизвести эту проблему), то вышеуказанный script завершится неудачно, и ваш обратный вызов никогда не будет вызван.

Ответ 8

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

tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); };

tag.onload = function(load) {/*init code here*/}

onreadystatechange Делегирование - из памяти - обходной путь для IE, который имеет неоднозначную поддержку для onload.

Ответ 9

то же самое, что и ответ Шона, но вместо создания тега script просто оцените его. это гарантирует, что код действительно готов к использованию.

Ответ 10

Моя стратегия, классический пример при загрузке jQuery UI, надеюсь, это поможет вам

( function( tools, libs ){
	
    // Iterator
    var require = function( scripts, onEnd ){
        
        onEnd = onEnd || function(){};
        
        if( !scripts || scripts.length < 1 )return onEnd();
        
        var src    = scripts.splice( 0, 1),
            script = document.createElement( "script" );
        
        script.setAttribute( "src", src );
        
        tools.addEvent( "load", script, function(){
            
            require( scripts, onEnd );
            
        } );
        
        document.getElementsByTagName( "head" )[ 0 ].appendChild( script );
        
    };
    
    // Install all scripts with a copy of scripts
    require( libs.slice(), function(){
    
        alert( "Enjoy :)" );
    
    } );
    
    // Timeout information
    var ti = setTimeout( function(){
        
        if( !window.jQuery || !window.jQuery.ui )alert( "Timeout !" );
        
        clearTimeout( ti );
        
    }, 5000 );

} )(

    { // Tools
    
        addEvent : function( evnt, elem, func ){
        
            try{

                if( elem.addEventListener ){

                    elem.addEventListener( evnt, func, false );

                }else if( elem.attachEvent ){

                     var r = elem.attachEvent( "on" + evnt, func );

                }

                return true;

            }catch( e ){

                return false;

            }		    

        }
    
    },
    [ // Scripts
    
        "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js",
        "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"
        
    ]

);

Ответ 11

При использовании Angular вы можете воспользоваться тем фактом, что каждый поставщик создается при создании других служб. Вы можете комбинировать этот факт с помощью xhr и eval(), как указано @Neil. Код будет выглядеть следующим образом:

app.provider('SomeScriptSyncLoader', function() {

    var resourceUrl =  'http://some/script.js';
    var dummy = {};

    this.$get = function() {

        var q = jQuery.ajax({
            type: 'GET', url: resourceUrl, cache: false, async: false
        });

        if (q.status === 200) {
            eval(q.responseText); // execute some script synchronously as inline script - eval forces sync processing
        }
        return dummy;
    };
});

Чтобы заставить провайдера быть инициализированным, вам необходимо ввести его, по крайней мере, в одну другую директиву/службу. Предпочтительно, это будет услуга, которая использует код, загруженный с помощью script.

app.directive('myDirective', ['SomeScriptSyncLoader', function(someScriptSyncLoader) {

return {
    restrict: 'E',
    link: function(scope, element, attrs) {
        // some ode
    },
    template: "this is my template"
   };
}]);

Ответ 12

Я знаю, что это старый вопрос, но, возможно, кто-то еще прочитает это и найдет его полезным! Просто созданный новый компонент использует ES6 для динамического синхронного загрузки сценариев. Детали проекта и исходный код находятся на GitHub https://github.com/amgadfahmi/scripty

Ответ 13

Возможно, я опаздываю, чтобы ответить на этот вопрос.

Мое текущее решение - это рекурсивно добавить теги <script>, чтобы добавление следующего script было в обратном вызове его предшественника. Он предполагает, что каждая функция содержит одну функцию, и эта функция совпадает с именем файла (минус расширение). Возможно, это не лучший способ сделать что-то, но он работает нормально.

Код для рассмотрения

Структура каталога каталогов:

- directory
---- index.html
---- bundle.js
---- test_module/
-------- a.js
-------- b.js
-------- log_num.js
-------- many_parameters.js

index.html

<head>
  <script src="bundle.js"></script>
</head>

bundle.js

// Give JS arrays the .empty() function prototype
if (!Array.prototype.empty){
    Array.prototype.empty = function(){
        return this.length == 0;
    };
};

function bundle(module_object, list_of_files, directory="") {
  if (!list_of_files.empty()) {
    var current_file = list_of_files.pop()
    var [function_name, extension] = current_file.split(".")
    var new_script = document.createElement("script")
    document.head.appendChild(new_script)

    new_script.src = directory + current_file

    new_script.onload = function() {
      module_object[function_name] = eval(function_name)
      bundle(module_object, list_of_files, directory)
      /*
      nullify the function in the global namespace as - assumed -  last
      reference to this function garbage collection will remove it. Thus modules
      assembled by this function - bundle(obj, files, dir) - must be called
      FIRST, else one risks overwritting a funciton in the global namespace and
      then deleting it
      */
      eval(function_name + "= undefined")
    }
  }
}

var test_module = {}
bundle(test_module, ["a.js", "b.js", "log_num.js", "many_parameters.js"], "test_module/")

a.js

function a() {
  console.log("a")
}

b.js

function b() {
  console.log("b")
}

log_num.js

// it works with parameters too
function log_num(num) {
  console.log(num)
}

many_parameters.js

function many_parameters(a, b, c) {
  var calc = a - b * c
  console.log(calc)
}

Ответ 14

Фактически существует способ загрузить список скриптов и выполнить их синхронно. Вам нужно вставить каждый тэг script в DOM, явно установив для атрибута async значение false:

script.async = false;

Сценарии, которые были введены в DOM, выполняются асинхронно по умолчанию, поэтому вам необходимо установить атрибут async на false вручную, чтобы обойти это.

Пример

<script>
(function() {
  var scriptNames = [
    "https://code.jquery.com/jquery.min.js",
    "example.js"
  ];
  for (var i = 0; i < scriptNames.length; i++) {
    var script = document.createElement('script');
    script.src = scriptNames[i];
    script.async = false; // This is required for synchronous execution
    document.head.appendChild(script);
  }
  // jquery.min.js and example.js will be run in order and synchronously
})();
</script>

<!-- Gotcha: these two script tags may still be run before `jquery.min.js`
     and `example.js` -->
<script src="example2.js"></script>
<script>/* ... */<script>

Ссылки

Ответ 15

Я использую метод загрузки jquery, примененный к элементу div. что-то вроде

<div id="js">
<!-- script will be inserted here --> 
</div>

...

$("#js").load("path", function() {  alert("callback!" });

Вы можете загружать скрипты несколько раз, и каждый раз, когда один script полностью заменит загруженный ранее