document.createElement (“script”) de forma síncrona

É possível chamar um arquivo .js síncrona e depois usá-lo imediatamente depois?

  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(); };  

Isso é simplificado. Na minha implementação, o material createElement está em uma function. Pensei em adicionar algo à function que poderia verificar se uma determinada variável foi instanciada antes de retornar o controle. Mas ainda há o problema do que fazer ao include js de outro site sobre o qual não tenho controle.

Pensamentos?

Editar:

Eu aceitei a melhor resposta por enquanto porque dá uma boa explicação para o que está acontecendo. Mas se alguém tiver alguma sugestão de como melhorar isso, estou aberto a eles. Aqui está um exemplo do que eu gostaria de fazer.

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

Eu só quero evitar ter que conhecer muito os internos e ser capaz de dizer: “Eu gostaria de usar este módulo, e agora eu vou usar algum código dele”.

Você pode criar seu elemento com um manipulador "onload", que será chamado quando o script tiver sido carregado e avaliado pelo navegador.

 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); 

Você não pode fazer isso de forma síncrona.

editar - tem sido apontado que, de forma fiel, o IE não triggers um evento "load" em tags sendo carregadas / avaliadas. Assim, suponho que a próxima coisa a fazer seja buscar o script com um XMLHttpRequest e depois eval() você mesmo. (Ou, suponho, coloque o texto em uma tag você adiciona; o ambiente de execução de eval() é afetado pelo escopo local, portanto, ele não fará necessariamente o que você quer que ele faça.)

editar - A partir do início de 2013 , aconselho fortemente que você procure uma ferramenta de carregamento de scripts mais robusta, como o Requirejs . Há muitos casos especiais para se preocupar. Para situações realmente simples, há o yepnope , que agora está embutido no Modernizr .

Isso não é bonito, mas funciona:

 ');   

Ou

 '); window.onload = function() { functionFromOther(); };  

O script deve ser incluído em uma tag separada ou antes de window.onload() .

Isso não funcionará:

 '); functionFromOther(); // Error  

O mesmo pode ser feito com a criação de um nó, como o Pointy, mas apenas no FF. Você não tem garantia de quando o script estará pronto em outros navegadores.

Sendo um purista XML eu realmente odeio isso. Mas funciona de maneira previsível. Você poderia facilmente embrulhar esses document.write() feiosos document.write() s para que você não tenha que olhar para eles. Você poderia até mesmo fazer testes e criar um nó e anexá-lo a fallback em document.write() .

Isso é muito tarde, mas para referência futura a qualquer um que queira fazer isso, você pode usar o seguinte:

 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); } 

Eu fiz um pequeno post sobre ele há algum tempo http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-to-a-page-and-be-notified-when-its -carregado/

A programação assíncrona é um pouco mais complicada porque a consequência de fazer uma solicitação é encapsulada em uma function em vez de seguir a instrução de solicitação. Mas o comportamento em tempo real que o usuário experimenta pode ser significativamente melhor, porque eles não verão um servidor lento ou uma rede lenta, fazendo com que o navegador aja como se estivesse travado. A programação síncrona é desrespeitosa e não deve ser empregada em aplicativos usados ​​por pessoas.

Douglas Crockford ( Blog da YUI )

Tudo bem, aperte seus assentos, porque vai ser um passeio acidentado. Mais e mais pessoas perguntam sobre o carregamento de scripts dinamicamente via javascript, parece ser um tema quente.

As principais razões pelas quais isso se tornou tão popular são:

  • modularidade do lado do cliente
  • gerenciamento de dependencies mais fácil
  • tratamento de erros
  • vantagens de desempenho

Sobre a modularidade : é óbvio que o gerenciamento de dependencies do lado do cliente deve ser tratado diretamente no lado do cliente. Se um determinado object, módulo ou biblioteca for necessário, basta pedir e carregá-lo dinamicamente.

Tratamento de erros : se um recurso falhar, ainda temos a chance de bloquear apenas as partes que dependem do script afetado, ou talvez até tentar novamente com algum atraso.

O desempenho tornou-se uma vantagem competitiva entre os sites, agora é um fator de sorting de pesquisa. O que os scripts dynamics podem fazer é imitar o comportamento asynchronous em oposição à maneira padrão de bloqueio de como os navegadores lidam com scripts. Os scripts bloqueiam outros resources, os scripts bloqueiam a análise adicional do documento HTML, os scripts bloqueiam a interface do usuário. Agora, com as tags de script dynamic e suas alternativas de vários navegadores, você pode fazer solicitações assíncronas reais e executar o código dependente somente quando elas estiverem disponíveis. Seus scripts serão carregados em paralelo, mesmo com outros resources, e o processamento será impecável.

A razão pela qual algumas pessoas aderem ao script síncrono é porque estão acostumadas a isso. Eles acham que é o caminho padrão, é o caminho mais fácil, e alguns podem até pensar que é o único caminho.

Mas a única coisa que devemos nos preocupar quando isso precisa ser decidido em relação ao design de um aplicativo é a experiência do usuário final . E nesta área assíncrona não pode ser derrotado. O usuário obtém respostas imediatas (ou promete), e uma promise é sempre melhor que nada. Uma canvas em branco assusta as pessoas. Os desenvolvedores não devem ter preguiça de melhorar o desempenho percebido .

E finalmente algumas palavras sobre o lado sujo. O que você deve fazer para funcionar nos navegadores:

  1. aprenda a pensar de forma assíncrona
  2. organize seu código para ser modular
  3. organize seu código para lidar com erros e casos de borda bem
  4. melhorar progressivamente
  5. sempre cuide da quantidade certa de feedback

As respostas acima me indicaram a direção certa. Aqui está uma versão genérica do que eu comecei a trabalhar:

  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 } 

Isso parece uma visão geral decente do carregamento dynamic de scripts: http://unixpapa.com/js/dyna.html

Eu tive o (s) seguinte (s) problema (s) com as respostas existentes para esta questão (e variações desta questão em outros threads do stackoverflow):

  • Nenhum dos códigos carregados era debuggable
  • Muitas das soluções exigiam retornos de chamada para saber quando o carregamento estava concluído, em vez de realmente bloquear, o que significa que eu obteria erros de execução imediatamente ao chamar o código carregado (isto é, carregamento).

Ou, um pouco mais precisamente:

  • Nenhum dos códigos carregados era debuggable (exceto a partir do bloco de tags de script HTML, se e somente se a solução adicionasse elementos de script ao dom, e nunca como scripts visualizáveis ​​individuais.) => Dado quantos scripts eu tenho que carregar ( e debugging), isso era inaceitável.
  • Soluções usando events ‘onreadystatechange’ ou ‘onload’ falharam em bloquear, o que foi um grande problema desde que o código originalmente carregou scripts dynamics sincronicamente usando ‘require ([filename,’ dojo / domReady ‘]);’ e eu estava tirando o dojo.

Minha solução final, que carrega o script antes de retornar, E tem todos os scripts adequadamente acessíveis no depurador (pelo menos para o Chrome) é a seguinte:

AVISO: O código a seguir deve ser usado apenas no modo ‘desenvolvimento’. (Para o modo ‘release’, recomendo pré-empacotamento e minificação SEM carregamento de script dynamic, ou pelo menos sem 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); } }; 
 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'); }) */ } 

Eu estou acostumado a ter vários arquivos .js no meu site que dependem um do outro. Para carregá-los e garantir que as dependencies sejam avaliadas na ordem correta, escrevi uma function que carrega todos os arquivos e, depois de recebidos, eval() . A principal desvantagem é que, uma vez que isso não funciona com o CDN. Para tais bibliotecas (por exemplo, jQuery), é melhor incluí-las estaticamente. Observe que inserir nós de script no HTML dinamicamente não garante que os scripts sejam avaliados na ordem correta, pelo menos não no Chrome (essa foi a principal razão para escrever essa function).

 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); } } 

Eu não descobri como fazer referências ao mesmo valor sem criar um array (para contagem). Caso contrário, eu acho que é auto-explicativo (quando tudo é carregado, eval() todos os arquivos na ordem dada, caso contrário, basta armazenar a resposta).

Exemplo de uso:

 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' ]) ; 

Ironicamente, eu tenho o que você quer, mas quero algo mais próximo do que você tem.

Estou carregando coisas dinamicamente e de forma assíncrona, mas com um retorno de chamada de load assim (usando dojo e xmlhtpprequest)

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

Para uma explicação mais detalhada, veja aqui

O problema é que em algum lugar ao longo da linha o código é avaliado, e se houver algo errado com seu código, o console.error(errorMessage); instrução indicará a linha em que eval() é, não o erro real. Este é um grande problema que eu estou realmente tentando converter de volta para instruções (veja aqui .