Document.createElement( "script" ) синхронно

Можно ли синхронно вызывать файл .js, а затем использовать его сразу после?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

Это упрощается. В моей реализации элемент createElement находится в функции. Я думал о добавлении чего-то к функции, которая могла бы проверить, была ли создана определенная переменная, прежде чем возвращать управление. Но тогда все еще есть проблема, что делать, если включить js с другого сайта, на который я не контролирую.

Мысли?

Edit:

Я принял лучший ответ, потому что он дает хорошее объяснение тому, что происходит. Но если у кого-то есть предложения по улучшению этого, я им открыт. Вот пример того, что я хотел бы сделать.

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

Я просто хочу не знать, что внутренности слишком много, и просто сказать: "Я хочу использовать этот модуль, и теперь я буду использовать какой-то код из него".

Ответы

Ответ 1

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

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

Вы не можете делать это синхронно.

edit — было указано, что, верно для формы, IE не запускает событие "load" на загружаемом/оцениваемом теге <script>. Таким образом, я предполагаю, что следующая задача - получить script с XMLHttpRequest, а затем eval() его самостоятельно. (Или, я полагаю, добавьте текст в тег <script>, который вы добавляете, среда выполнения eval() зависит от локальной области, поэтому она не обязательно будет делать то, что вы хотите, чтобы она делала.)

edit — Начиная с начала 2013 года, я настоятельно рекомендую изучить более надежный инструмент загрузки script, например Requirejs, Есть много особых случаев, о которых нужно беспокоиться. Для действительно простых ситуаций yepnope, который теперь встроен в Modernizr.

Ответ 2

Это не очень, но он работает:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

или

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

script должен быть включен либо в отдельный тег <script>, либо перед window.onload().

Это не сработает:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

То же самое можно сделать при создании node, как это делал Уокти, но только в FF. У вас нет гарантии, когда script будет готов в других браузерах.

Будучи пуристом XML, я действительно ненавижу это. Но это работает предсказуемо. Вы можете легко обернуть эти уродливые document.write(), чтобы не смотреть на них. Вы даже можете выполнить тесты и создать node и добавить его, а затем вернуться на document.write().

Ответ 3

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

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

Я сделал короткое сообщение в блоге на нем некоторое время назад http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/

Ответ 4

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

Дуглас Крокфорд (Блог YUI)

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

Основными причинами, по которым это стало настолько популярным, являются:

  • модульность на стороне клиента
  • упрощение управления зависимостями
  • обработка ошибок
  • Преимущества производительности

О модульности: очевидно, что управление зависимостями на стороне клиента следует обрабатывать прямо на стороне клиента. Если требуется определенный объект, модуль или библиотека, мы просто запрашиваем его и загружаем его динамически.

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

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

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

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

И, наконец, несколько слов о грязной стороне. Что вы должны сделать, чтобы заставить его работать в разных браузерах:

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

Ответ 6

Ответы выше указывали мне в правильном направлении. Вот общая версия того, что я получил:

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      

Ответ 7

function include(file){
return new Promise(function(resolve, reject){
        var script = document.createElement('script');
        script.src = file;
        script.type ='text/javascript';
        script.defer = true;
        document.getElementsByTagName('head').item(0).appendChild(script);

        script.onload = function(){
        resolve()
        }
        script.onerror = function(){
          reject()
        }
      })

 /*I HAVE MODIFIED THIS TO  BE PROMISE-BASED 
   HOW TO USE THIS FUNCTION 

  include('js/somefile.js').then(function(){
  console.log('loaded');
  },function(){
  console.log('not loaded');
  })
  */
}

Ответ 8

Я использую несколько файлов .js на своем веб-сайте, которые зависят друг от друга. Чтобы загрузить их и убедиться, что зависимости оцениваются в правильном порядке, я написал функцию, которая загружает все файлы, а затем, после их получения, eval() их. Главный недостаток заключается в том, что, поскольку это не работает с CDN. Для таких библиотек (например, jQuery) лучше включить их статически. Обратите внимание, что вставка script узлов в HTML динамически не гарантирует, что скрипты будут оцениваться в правильном порядке, по крайней мере, не в Chrome (это была основная причина для написания этой функции).

function xhrs(reqs) {
  var requests = [] , count = [] , callback ;

  callback = function (r,c,i) {
    return function () {
      if  ( this.readyState == 4 ) {
        if (this.status != 200 ) {
          r[i]['resp']="" ;
        } 
        else {
          r[i]['resp']= this.responseText ;
        }
        c[0] = c[0] - 1 ;
        if ( c[0] == 0 ) {
          for ( var j = 0 ; j < r.length ; j++ ) {
            eval(r[j]['resp']) ;
          }
        }
      }
    }
  } ;
  if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
    requests.length = reqs.length ;
  }
  else {
    requests.length = 1 ;
    reqs = [].concat(reqs);
  }
  count[0] = requests.length ;
  for ( var i = 0 ; i < requests.length ; i++ ) {
    requests[i] = {} ;
    requests[i]['xhr'] = new XMLHttpRequest () ;
    requests[i]['xhr'].open('GET', reqs[i]) ;
    requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
    requests[i]['xhr'].send(null);
  }
}

Я не понял, как создавать ссылки на одно и то же значение, не создавая массив (для count). В противном случае я думаю, что это самоочевидно (когда все загружено, eval() каждый файл в указанном порядке, иначе просто сохраните ответ).

Пример использования:

xhrs( [
       root + '/global.js' ,
       window.location.href + 'config.js' ,
       root + '/js/lib/details.polyfill.min.js',
       root + '/js/scripts/address.js' ,
       root + '/js/scripts/tableofcontents.js' 
]) ;

Ответ 9

У меня была следующая проблема с существующими ответами на этот вопрос (и варианты этого вопроса в других потоках 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);
    }
};

Ответ 10

По иронии судьбы, у меня есть то, что вы хотите, но хотите что-то ближе к тому, что у вас было.

Я загружаю вещи динамически и асинхронно, но с обратным вызовом load вроде этого (используя dojo и xmlhtpprequest)

  dojo.xhrGet({
    url: 'getCode.php',
    handleAs: "javascript",
    content : {
    module : 'my.js'
  },
  load: function() {
    myFunc1('blarg');
  },
  error: function(errorMessage) {
    console.error(errorMessage);
  }
});

Более подробное объяснение см. здесь

Проблема заключается в том, что где-то вдоль строки код получает оценку, а если что-то не так с вашим кодом, оператор console.error(errorMessage); будет указывать строку, где eval(), а не фактическая ошибка. Это ТАКОЙ большой проблемой, которую я фактически пытаюсь преобразовать в выражения <script> (см. здесь.