Javascript, выполнить код сценариев, чтобы вставить в дерево

НЕ ДУБЛИКАТ, КАК Я ДОЛЖЕН НАЙТИ УДОВЛЕТВОРИТЕЛЬНЫЙ ОТВЕТ НА ДРУГИЕ РЕЗЬБЫ:


Ищите собственные Javascript ответы, нет jQuery, no requireJS и т.д., пожалуйста:)


РЕЗЮМЕ ВСЕГО ВОПРОСА:

Я хочу асинхронно загружать скрипты, но заказывать исполнение

Я пытаюсь обеспечить, чтобы код во вставленных элементах Script выполнялся точно в том же порядке, поскольку они были добавлены в дерево dom.

То есть , если я вставляю два Script тега, первый и второй, любой код в первом должен стрелять до второго, независимо от того, кто заканчивает загрузку первым.

Я пробовал с атрибутом async и отложить при вставке в голову, но, похоже, не подчиняется.

Я попытался использовать element.setAttribute( "defer", ") и element.setAttribute(" async", false) и другие комбинации.

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

Второй script, который является локальным , всегда запускается перед первым, даже если он вставлен впоследствии в дереве dom (head).

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

Я понимаю, что RequireJS, похоже, делает именно это, поэтому это должно быть возможно. Однако requireJS может вытащить его, выполнив его, как описано в A).

Код, если вы хотите попробовать прямо в firebug, просто скопируйте и вставьте:

    function loadScript(path, callback, errorCallback, options) {
            var element = document.createElement('script');
            element.setAttribute("type", 'text/javascript');
            element.setAttribute("src", path);

            return loadElement(element, callback, errorCallback, options);
    }

    function loadElement(element, callback, errorCallback, options) {
            element.setAttribute("defer", "");
            // element.setAttribute("async", "false");

            element.loaded = false;

            if (element.readyState){  // IE
                    element.onreadystatechange = function(){
                            if (element.readyState == "loaded" || element.readyState == "complete"){
                                    element.onreadystatechange = null;

                                    loadElementOnLoad(element, callback);
                            }
                    };
            } else {                 // Others
                    element.onload = function() {
                            loadElementOnLoad(element, callback);
                    };
            }

            element.onerror = function() {
                    errorCallback && errorCallback(element);
            };

            (document.head || document.getElementsByTagName('head')[0] || document.body).appendChild(element);

            return element;
    }

    function loadElementOnLoad(element, callback) {
            if (element.loaded != true) {
                    element.loaded = true;
                    if ( callback ) callback(element);
            }
    }




loadScript("http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js",function() {
  alert(1);  
})

loadScript("http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",function() {
  alert(2);  
})

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

Ответы

Ответ 1

Хорошо, я думаю, что у меня есть решение.

Фокус в том, что мы отслеживаем каждый загружаемый script и их порядок, когда мы вставляем их в дерево dom. Каждый из их обратных вызовов затем регистрируется соответственно их элементу.

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

var stack = [];
stack.loaded = 0;
function loadScriptNew(path, callback) {
        var o = { callback: callback };
        stack.push(o);

        loadScript(path, function() {
                o.callbackArgs = arguments;
                stack.loaded++;
                executeWhenReady();
        });
}

function executeWhenReady() {
        if ( stack.length == stack.loaded ) {
                while(stack.length) {
                        var o = stack.pop();
                        o.callback.apply(undefined, o.callbackArgs);
                }

                stack.loaded = 0;
        }
}

//Выше было то, что было добавлено в код в вопросе.

function loadScript(path, callback) {
        var element = document.createElement('script');
        element.setAttribute("type", 'text/javascript');
        element.setAttribute("src", path);

        return loadElement(element, callback);
}

function loadElement(element, callback) {
        element.setAttribute("defer", "");
        // element.setAttribute("async", "false");

        element.loaded = false;

        if (element.readyState){  // IE
                element.onreadystatechange = function(){
                        if (element.readyState == "loaded" || element.readyState == "complete"){
                                element.onreadystatechange = null;

                                loadElementOnLoad(element, callback);
                        }
                };
        } else {                 // Others
                element.onload = function() {
                        loadElementOnLoad(element, callback);
                };
        }

        (document.head || document.getElementsByTagName('head')[0] || document.body).appendChild(element);

        return element;
}

function loadElementOnLoad(element, callback) {
        if (element.loaded != true) {
                element.loaded = true;
                if ( callback ) callback(element);
        }
}

loadScriptNew("http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js",function() {
        alert(1);
});

loadScriptNew("http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",function() {
        alert(2);
});

Хорошо, некоторые из вас могут утверждать, что в вопросе отсутствует информация, которую я вам дам, и здесь мы фактически решаем обратный вызов. Ты прав. Код в script все еще выполняется в неправильном порядке, но обратный вызов теперь.

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

Я все еще надеюсь на Асада и его решение iframe, которое, я считаю, может дать лучший ответ на этот вопрос. Для меня, однако, это решение решит мои проблемы:)

Ответ 2

если я вставляю два тега script, первый и второй, любой код сначала должен стрелять до второго, независимо от того, кто первым заканчивает загрузку. Я попытался с атрибутом async и отменил атрибут

Нет, async и defer вам не помогут. Всякий раз, когда вы динамически вставляете script элементы в DOM, они загружаются и выполняются асинхронно. Вы ничего не можете сделать против этого.

Я понимаю, что RequireJS, похоже, делает именно это

Нет. Даже с RequireJS скрипты выполняются асинхронно и не в порядке. Только функции инициализатора модуля в этих сценариях просто define() d, а не выполняются. Requirejs тогда смотрит, когда их зависимости удовлетворяются и исполняет их позже, когда загружаются другие модули.

Конечно, вы можете изобрести колесо, но вам придется идти с подобной структурой.

Ответ 3

Через некоторое время возиться с ним, вот что я придумал. Запросы для скриптов отправляются немедленно, но они выполняются только в указанном порядке.

Алгоритм:

Алгоритм состоит в том, чтобы поддерживать дерево (у меня не было времени реализовать это: прямо сейчас это просто вырожденный случай списка) скриптов, которые нужно выполнить. Запросы на все они отправляются почти одновременно. Каждый раз, когда загружается script, происходят две вещи: 1) script добавляется в плоский список загружаемых сценариев и 2) идет вниз от корня node, столько скриптов в каждой ветки, которая загружается но не были выполнены.

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

Реализация:

Для демонстрационных целей я повторяю назад массив scriptsToExecute, так что запрос для CFInstall отправляется перед запросом для angularJS. Это не обязательно означает, что CFInstall будет загружаться до angularJS, но есть больше шансов на это. Независимо от этого, angularJS всегда будет оцениваться до CFInstall.

Обратите внимание, что я использовал jQuery, чтобы упростить мою жизнь, создавая элемент iframe и назначая обработчик нагрузки, но вы можете записать это без jQuery:

// The array of scripts to load and execute

var scriptsToExecute = [
    "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js?t=" + Date.now(), 
    "http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js?t=" + Date.now()
];


// Loaded scripts are stored here

var loadedScripts = {};


// For demonstration purposes, the requests are sent in reverse order.
// They will still be executed in the order specified in the array.

(function start() {
    for (var i = scriptsToExecute.length - 1; i >= 0; i--) {
        (function () {
            var addr = scriptsToExecute[i];
            requestData(addr, function () {
                console.log("loaded " + addr);
            });
        })();
    }
})();


// This function executes as many scripts as it currently can, by
// inserting script tags with the corresponding src attribute. The
// scripts aren't reloaded, since they are in the cache. You could
// alternatively eval `script.code`

function executeScript(script) {
    loadedScripts[script.URL] = script.code

    while (loadedScripts.hasOwnProperty(scriptsToExecute[0])) {
        var scriptToRun = scriptsToExecute.shift()
        var element = document.createElement('script');
        element.setAttribute("type", 'text/javascript');
        element.setAttribute("src", scriptToRun);

        $('head').append(element);

        console.log("executed " + scriptToRun);
    }
}


// This function fires off a request for a script

function requestData(path, loadCallback) {
    var iframe = $("<iframe/>").load(function () {
        loadCallback();
        executeScript({
            URL: $(this).attr("src"),
            code: $(this).html()
        });
    }).attr({"src" : path, "display" : "none"}).appendTo($('body'));
}

Здесь вы можете увидеть демо здесь. Соблюдайте консоль.

Ответ 4

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

function loadScript(order, path) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET",path,true);
    xhr.send();
    xhr.onreadystatechange = function(){
        if(xhr.readyState  == 4){
            if(xhr.status >= 200 && xhr.status < 300 || xhr == 304){
                loadedScripts[order] = xhr.responseText;
            }
            else {
                //deal with error
                loadedScripts[order] = 'alert("this is a failure to load script '+order+'");';
                // or  loadedScripts[order] = '';  // this smoothly fails
            }
            alert(order+' - '+xhr.status+' > '+xhr.responseText);                // this is to show the completion order.  Careful, FF stacks aletrs so you see in reverse.
            // am I the last one ???
            executeAllScripts();
        }
    };
}

function executeAllScripts(){
    if(loadedScripts.length!=scriptsToLoad.length) return;
    for(var a=0; a<loadedScripts.length; a++) eval(loadedScripts[a]);
    scriptsToLoad = [];
}


var loadedScripts = [];
var scriptsToLoad = [
   "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js",
   "http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",
   "http://nowhere.existing.real_script.com.ar/return404.js"
];

// load all   even in reverse order ... or randomly
for(var a=0; a<scriptsToLoad.length; a++) loadScript(a, scriptsToLoad[a]);

Ответ 5

Не можете ли вы загружать загрузку с помощью обратных вызовов ur?

т

loadScript("http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js",function() {
    alert(1);
    loadScript("http://ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js",function() {
        alert(2);  
    })
})