Como incluir vários arquivos js usando o método jQuery $ .getScript ()

127

Estou tentando incluir dinamicamente arquivos javascript no meu arquivo js. Eu fiz algumas pesquisas sobre o assunto e descobri que o método jQuery $ .getScript () seria o caminho desejado.

// jQuery
$.getScript('/path/to/imported/script.js', function()
{
    // script is now loaded and executed.
    // put your dependent JS here.
    // what if the JS code is dependent on multiple JS files? 
});

Mas eu estou querendo saber se este método pode carregar vários scripts ao mesmo tempo? Por que estou perguntando isso é porque às vezes meu arquivo javascript depende de mais de um arquivo js.

Agradeço antecipadamente.

Sozhen
fonte

Respostas:

315

A resposta é

Você pode usar promessas com getScript()e aguardar até que todos os scripts sejam carregados, algo como:

$.when(
    $.getScript( "/mypath/myscript1.js" ),
    $.getScript( "/mypath/myscript2.js" ),
    $.getScript( "/mypath/myscript3.js" ),
    $.Deferred(function( deferred ){
        $( deferred.resolve );
    })
).done(function(){
    
    //place your code here, the scripts are all loaded
    
});

FIDDLE

OUTRO FIDDLE

No código acima, adicionar um adiado e resolvê-lo $()é como colocar qualquer outra função dentro de uma chamada jQuery, como $(func)é o mesmo que

$(function() { func(); });

ou seja, ele espera o DOM estar pronto, portanto, no exemplo acima, $.whenespera que todos os scripts sejam carregados e o DOM esteja pronto devido à $.Deferredchamada que é resolvida no retorno de chamada pronto do DOM.


Para uso mais genérico, uma função útil

Uma função utilitária que aceita qualquer matriz de scripts pode ser criada assim:

$.getMultiScripts = function(arr, path) {
    var _arr = $.map(arr, function(scr) {
        return $.getScript( (path||"") + scr );
    });
        
    _arr.push($.Deferred(function( deferred ){
        $( deferred.resolve );
    }));
        
    return $.when.apply($, _arr);
}

que pode ser usado assim

var script_arr = [
    'myscript1.js', 
    'myscript2.js', 
    'myscript3.js'
];

$.getMultiScripts(script_arr, '/mypath/').done(function() {
    // all scripts loaded
});

onde o caminho será anexado a todos os scripts e também é opcional, o que significa que, se a matriz contiver o URL completo, também será possível fazer isso e deixar o caminho todo junto

$.getMultiScripts(script_arr).done(function() { ...

Argumentos, erros etc.

Como um aparte, observe que o doneretorno de chamada conterá vários argumentos correspondentes aos scripts passados, cada argumento representando uma matriz que contém a resposta

$.getMultiScripts(script_arr).done(function(response1, response2, response3) { ...

onde cada matriz conterá algo como [content_of_file_loaded, status, xhr_object]. Geralmente, não precisamos acessar esses argumentos, pois os scripts serão carregados automaticamente de qualquer maneira, e na maioria das vezes o doneretorno de chamada é tudo o que realmente precisamos saber para saber que todos os scripts foram carregados, estou apenas adicionando-o para completar e nas raras ocasiões em que o texto real do arquivo carregado precisa ser acessado ou quando é necessário acessar cada objeto XHR ou algo semelhante.

Além disso, se algum dos scripts falhar ao carregar, o manipulador de falhas será chamado e os scripts subsequentes não serão carregados

$.getMultiScripts(script_arr).done(function() {
     // all done
}).fail(function(error) {
     // one or more scripts failed to load
}).always(function() {
     // always called, both on success and error
});
adeneo
fonte
11
Obrigado pela ótima resposta. Você se importaria de me explicar por que adicionar $.Deferred(function( deferred ){$( deferred.resolve );})aqui?
sozhen
11
O objeto adiado realmente não tem nada a ver com a promessa que permite carregar vários scripts e executar uma função quando tudo estiver pronto. Ele apenas verifica se $ () está pronto e resolve se está, ou seja, verifica se o DOM está pronto antes de executar qualquer código, como document.ready estaria, e encontrei um bom exemplo de anexação promete obter o getScript on-line que tenha a funcionalidade adiada para DOM pronta no código, e apenas decidiu mantê-lo, pois parecia uma boa idéia.
adeneo 3/08/12
4
Isso ocorre porque o cache no ajax é desativado por padrão no jQuery, para ativá-lo e remover a consulta: $.ajaxSetup({ cache: true });mas isso também pode afetar outras chamadas ajax que você não deseja armazenar em cache, há muito mais sobre isso nos documentos para getScript e ainda há um pequeno tutorial sobre como criar uma função getScript em cache chamada cachedScript.
adeneo
3
Desculpe pela aceitação tardia. Às vezes, seu caminho ainda não está funcionando. Não tenho certeza qual é o motivo. Estou tentando incorporar quatro arquivos de script ao mesmo tempo. $.when($.getScript(scriptSrc_1), $.getScript(scriptSrc_2), $.getScript(scriptSrc_3), $.getScript(scriptSrc_4), $.Deferred(function(deferred) { $(deferred.resolve); })).done(function() { ... })
sozhen
1
Para quem não consegue fazer isso funcionar, verifique se não há erros de análise nos arquivos JS, ou seja, teste se eles carregam corretamente em uma tag <script> primeiro. O jQuery.getScript () considerará esses erros como falha no carregamento e .fail () será chamado em vez de .done (). @SongtaoZ.
poder do prisma da lua 04/04
29

Eu implementei uma função simples para carregar vários scripts em paralelo:

Função

function getScripts(scripts, callback) {
    var progress = 0;
    scripts.forEach(function(script) { 
        $.getScript(script, function () {
            if (++progress == scripts.length) callback();
        }); 
    });
}

Uso

getScripts(["script1.js", "script2.js"], function () {
    // do something...
});
Andrei
fonte
1
Por alguma razão, este funcionou melhor que o de Emanuel. É uma implementação muito transparente também.
Todd
@satnam thanks :)! A resposta principal não funciona para algumas pessoas (inclusive eu), então eu postei esta solução. E parece muito mais simples.
Andrei
10

Carregue o seguinte script necessário no retorno de chamada do anterior, como:

$.getScript('scripta.js', function()
{
   $.getScript('scriptb.js', function()
   {
       // run script that depends on scripta.js and scriptb.js
   });
});
Frankey
fonte
1
Certamente esse método baixará os scripts sequencialmente, diminuindo o tempo para a execução final?
Jimbo
1
Correto, @ Jimbo - para ser justo; é preferível à execução prematura. :)
Alastair
Cachoeiras quase sempre devem ser evitadas. O getScript vem com uma promessa ... Você pode usar $ .when para ouvir quando as promessas resolverem.
Roi
@Roi e mais alguém: se eu preciso que os scripts sejam carregados em uma ORDEM ESPECÍFICA, o que, basicamente, está errado com essa abordagem? E que vantagem, portanto, traz a alternativa promissora?
Ifedi Okonkwo
@IfediOkonkwo isso funciona quando eles precisam estar em série, mas a origem acabou de mencionar que ele gostaria de múltiplos de uma só vez (no qual, isso seria um antipadrão). Mesmo assim, se os quisesse em série, escreveria uma função de loop para a qual poderia passar uma matriz. O assentamento profundo fica confuso rapidamente.
Roi
9

Às vezes, é necessário carregar scripts em uma ordem específica. Por exemplo, o jQuery deve ser carregado antes da interface do usuário do jQuery. A maioria dos exemplos desta página carrega scripts em paralelo (assincronamente), o que significa que a ordem de execução não é garantida. Sem pedidos, o script yque depende xpode ser interrompido se ambos forem carregados com êxito, mas na ordem errada.

Proponho uma abordagem híbrida que permite o carregamento seqüencial de scripts dependentes + carregamento paralelo opcional + objetos adiados :

/*
 * loads scripts one-by-one using recursion
 * returns jQuery.Deferred
 */
function loadScripts(scripts) {
  var deferred = jQuery.Deferred();

  function loadScript(i) {
    if (i < scripts.length) {
      jQuery.ajax({
        url: scripts[i],
        dataType: "script",
        cache: true,
        success: function() {
          loadScript(i + 1);
        }
      });
    } else {
      deferred.resolve();
    }
  }
  loadScript(0);

  return deferred;
}

/*
 * example using serial and parallel download together
 */

// queue #1 - jquery ui and jquery ui i18n files
var d1 = loadScripts([
  "https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min.js",
  "https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/i18n/jquery-ui-i18n.min.js"
]).done(function() {
  jQuery("#datepicker1").datepicker(jQuery.datepicker.regional.fr);
});

// queue #2 - jquery cycle2 plugin and tile effect plugin
var d2 = loadScripts([
  "https://cdn.rawgit.com/malsup/cycle2/2.1.6/build/jquery.cycle2.min.js",
  "https://cdn.rawgit.com/malsup/cycle2/2.1.6/build/plugin/jquery.cycle2.tile.min.js"

]).done(function() {
  jQuery("#slideshow1").cycle({
    fx: "tileBlind",
    log: false
  });
});

// trigger a callback when all queues are complete
jQuery.when(d1, d2).done(function() {
  console.log("All scripts loaded");
});
@import url("https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/blitzer/jquery-ui.min.css");

#slideshow1 {
  position: relative;
  z-index: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<p><input id="datepicker1"></p>

<div id="slideshow1">
  <img src="https://dummyimage.com/300x100/FC0/000">
  <img src="https://dummyimage.com/300x100/0CF/000">
  <img src="https://dummyimage.com/300x100/CF0/000">
</div>

Os scripts nas duas filas serão baixados em paralelo; no entanto, os scripts em cada fila serão baixados em sequência, garantindo a execução ordenada. Gráfico em cascata:

gráfico em cascata de scripts

Salman A
fonte
Não precisamos adicionar a tag de script em HTML depois que o script é carregado na memória?
Ankush Jain
4

Use yepnope.js ou Modernizr (que inclui yepnope.js como Modernizr.load).

ATUALIZAR

Apenas para acompanhar, aqui está um bom equivalente ao que você tem atualmente usando o yepnope, mostrando dependências em vários scripts:

yepnope({
  load: ['script1.js', 'script2.js', 'script3.js'],
  complete: function () {
      // all the scripts have loaded, do whatever you want here
  }
});
Chris Pratt
fonte
Obrigado. Posso usar vários yepnope.injectJs( scriptSource )no início do meu arquivo javascript, como incluir <script>tags no arquivo html?
sozhen
Isso é novo e, sinceramente, não entendo por que é necessário. Siga a documentação um pouco além da lista de alterações recentes e você verá o uso mais convencional.
Chris Pratt
+1; vale a pena notar que isso gerencia adequadamente dependências (ou seja, não carregará o mesmo script duas vezes) diferentemente $.getScript. Este é um grande negócio.
ov
O yepnope.js agora está obsoleto .
Stephan
4

Encontrei vários problemas com o carregamento de vários scripts, incluindo um (pelo menos no Chrome) o mesmo domínio de carregamento a quente de scripts que não estavam em execução após serem carregados com sucesso pelo Ajax, onde o Cross Domain funciona perfeitamente! :(

A resposta selecionada para a pergunta original não funciona de maneira confiável.

Depois de muitas iterações, aqui está minha resposta final ao getScript (s) e ao carregar múltiplos scripts de forma assíncrona em uma ordem estrita específica com a opção de retorno de chamada carregada por script e retorno de chamada geral após a conclusão, Testado no jQuery 2.1+ e nas versões modernas do Chrome, Firefox e o Internet Explorer abandonado.

Meu caso de teste estava carregando arquivos para uma renderização THREE.JS webGL e iniciando o script de renderização quando THREE global ficou disponível usando uma verificação de intervalo passada para uma chamada de função anônima para onComplete.

A função Prototype (getScripts)

function getScripts( scripts, onScript, onComplete )
{
    this.async = true;
    this.cache = false;
    this.data = null;
    this.complete = function () { $.scriptHandler.loaded(); };
    this.scripts = scripts;
    this.onScript = onScript;
    this.onComplete = onComplete;
    this.total = scripts.length;
    this.progress = 0;
};

getScripts.prototype.fetch = function() {
    $.scriptHandler = this;
    var src = this.scripts[ this.progress ];
    console.log('%cFetching %s','color:#ffbc2e;', src);

    $.ajax({
        crossDomain:true,
        async:this.async,
        cache:this.cache,
        type:'GET',
        url: src,
        data:this.data,
        statusCode: {
            200: this.complete
        },
        dataType:'script'
    });
};

getScripts.prototype.loaded = function () {
    this.progress++;
    if( this.progress >= this.total ) {
        if(this.onComplete) this.onComplete();
    } else {
        this.fetch();
    };
    if(this.onScript) this.onScript();
};

Como usar

var scripts = new getScripts(
    ['script1.js','script2.js','script.js'],
    function() {
        /* Optional - Executed each time a script has loaded (Use for Progress updates?) */
    },
    function () {
        /* Optional - Executed when the entire list of scripts has been loaded */
    }
);
scripts.fetch();

A função é a que encontrei, usando Adiado (Preterido agora?), Quando, Sucesso e Concluído em minhas tentativas para NÃO ser 100% confiável! ?, Portanto, esta função e o uso do statusCode, por exemplo.

Você pode querer adicionar um comportamento de manipulação de erro / falha, se desejar.

Marc
fonte
+1 para carregá-los na ordem correta, geralmente essencial, e algo que a resposta aceita não garantirá, a menos que esteja faltando alguma coisa? Publiquei uma resposta mais curta semelhante abaixo, mas este é um caso mais geral.
Whelkaholism
2

Você pode fazer uso do $.whenmétodo, tentando a seguinte função:

function loadScripts(scripts) {
  scripts.forEach(function (item, i) {
    item = $.getScript(item);
  });
  return $.when.apply($, scripts);
}

Esta função seria usada assim:

loadScripts(['path/to/script-a.js', 'path/to/script-b.js']).done(function (respA, respB) {
    // both scripts are loaded; do something funny
});

Essa é a maneira de usar o Promises e ter um mínimo de sobrecarga.

Emanuel Kluge
fonte
Isso garante que os scripts sejam carregados em sequência?
CyberMonk
2

Ótima resposta, adeneo.

Demorei um pouco para descobrir como tornar sua resposta mais genérica (para que eu pudesse carregar uma matriz de scripts definidos por código). O retorno de chamada é chamado quando todos os scripts são carregados e executados. Aqui está a minha solução:

    function loadMultipleScripts(scripts, callback){
        var array = [];

        scripts.forEach(function(script){
            array.push($.getScript( script ))
        });

        array.push($.Deferred(function( deferred ){
                    $( deferred.resolve );
                }));

        $.when.apply($, array).done(function(){
                if (callback){
                    callback();
                }
            });
    }
RoccoB
fonte
2

Anexar scripts com async = false

Aqui está uma abordagem diferente, mas super simples. Para carregar vários scripts, você pode simplesmente anexá-los ao corpo.

  • Carrega-os de forma assíncrona, porque é assim que os navegadores otimizam o carregamento da página
  • Executa scripts em ordem, porque é assim que os navegadores analisam as tags HTML
  • Não há necessidade de retorno de chamada, porque os scripts são executados em ordem. Basta adicionar outro script, e ele será executado após os outros scripts

Mais informações aqui: https://www.html5rocks.com/en/tutorials/speed/script-loading/

var scriptsToLoad = [
   "script1.js", 
   "script2.js",
   "script3.js",
]; 
    
scriptsToLoad.forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.body.appendChild(script);
});
Maciej Sawicki
fonte
1

O que você está procurando é um carregador compatível com AMD (como require.js).

http://requirejs.org/

http://requirejs.org/docs/whyamd.html

Existem muitos bons de código aberto, se você procurar. Basicamente, isso permite que você defina um módulo de código e, se depender de outros módulos de código, aguardará até o término do download desses módulos antes de continuar a execução. Dessa forma, você pode carregar 10 módulos de forma assíncrona e não deve haver problemas, mesmo que um dependa de alguns dos outros para execução.

dqhendricks
fonte
1

Esta função garantirá que um arquivo seja carregado após o carregamento completo do arquivo de dependência. Você só precisa fornecer os arquivos em uma sequência, tendo em mente as dependências de outros arquivos.

function loadFiles(files, fn) {
    if (!files.length) {
        files = [];
    }
    var head = document.head || document.getElementsByTagName('head')[0];

    function loadFile(index) {
        if (files.length > index) {
            var fileref = document.createElement('script');
            fileref.setAttribute("type", "text/javascript");
            fileref.setAttribute("src", files[index]);
            head.appendChild(fileref);
            index = index + 1;
            // Used to call a callback function
            fileref.onload = function () {
                loadFile(index);
            }
        } else if(fn){
            fn();
        }
    }
    loadFile(0);
}
Senhor Verde
fonte
Talvez seja melhor que a resposta aceita, pois carrega os arquivos em ordem. Se o script2 exigir que o script1 seja carregado, a ordem será essencial.
Salman A
1

Isso funciona para mim:

function getScripts(scripts) {
    var prArr = [];
    scripts.forEach(function(script) { 
        (function(script){
            prArr .push(new Promise(function(resolve){
                $.getScript(script, function () {
                    resolve();
                });
            }));
        })(script);
    });
    return Promise.all(prArr, function(){
        return true;
    });
}

E use-o:

var jsarr = ['script1.js','script2.js'];
getScripts(jsarr).then(function(){
...
});
user2020001
fonte
1

Aqui está a resposta usando a de Maciej Sawicki e implementando Promisecomo retorno de chamada:

function loadScripts(urls, path) {
    return new Promise(function(resolve) {
        urls.forEach(function(src, i) {

            let script = document.createElement('script');        
            script.type = 'text/javascript';
            script.src = (path || "") + src;
            script.async = false;

            // If last script, bind the callback event to resolve
            if(i == urls.length-1) {                    
                // Multiple binding for browser compatibility
                script.onreadystatechange = resolve;
                script.onload = resolve;
            }

            // Fire the loading
            document.body.appendChild(script);
        });
    });
}

Usar:

let JSDependencies = ["jquery.js",
                      "LibraryNeedingJquery.js",
                      "ParametersNeedingLibrary.js"];

loadScripts(JSDependencies,'JavaScript/').then(taskNeedingParameters);

Todos os arquivos Javascript são baixados o mais rápido possível e executados na ordem especificada. Então taskNeedingParametersé chamado.

H4dr1en
fonte
0

Versão mais curta da resposta abrangente de Andrew Marc Newton acima. Este não verifica o código de status para obter êxito, o que você deve fazer para evitar um comportamento indefinido da interface do usuário.

Este era para um sistema irritante, onde eu poderia garantir o jQuery, mas nenhum outro inclui, então eu queria uma técnica curta o suficiente para não ser criada em um script externo, se forçada a fazê-lo. (Você pode torná-lo ainda mais curto passando o índice 0 para a primeira chamada "recursiva", mas a força dos hábitos de estilo me fez adicionar o açúcar).

Também estou atribuindo a lista de dependências a um nome de módulo, para que esse bloco possa ser incluído em qualquer lugar em que você precise de "module1" e os scripts e a inicialização dependente serão incluídos / executados apenas uma vez (você pode efetuar login indexna chamada de retorno e ver uma única ordem conjunto de solicitações AJAX em execução)

if(typeof(__loaders) == 'undefined') __loaders = {};

if(typeof(__loaders.module1) == 'undefined')
{
    __loaders.module1 = false;

    var dependencies = [];

    dependencies.push('/scripts/loadmefirst.js');
    dependencies.push('/scripts/loadmenext.js');
    dependencies.push('/scripts/loadmelast.js');

    var getScriptChain  = function(chain, index)        
    {
        if(typeof(index) == 'undefined')
            index = 0;

        $.getScript(chain[index], 
            function()
            {
                if(index == chain.length - 1)
                {
                    __loaders.module1 = true;

                    /* !!!
                        Do your initialization of dependent stuff here 
                    !!! */
                }
                else 
                    getScriptChain(chain, index + 1);
            }
        );
    };

    getScriptChain(dependencies);       
}
Whelkaholism
fonte
0

Existe um plugin que estende o método getScript do jQuery. Permite carregamento assíncrono e síncrono e usa o mecanismo de cache do jQuery. Divulgação completa, eu escrevi isso. Sinta-se à vontade para contribuir se você encontrar um método melhor.

https://github.com/hudsonfoo/jquery-getscripts

user3455212
fonte
0

Carrega n scripts um por um (útil se, por exemplo, o segundo arquivo precisar do primeiro):

(function self(a,cb,i){
    i = i || 0; 
    cb = cb || function(){};    
    if(i==a.length)return cb();
    $.getScript(a[i++],self.bind(0,a,cb,i));                    
})(['list','of','script','urls'],function(){console.log('done')});

fonte
0

com base na resposta de @adeneo acima: Combinando o carregamento de arquivos css e js

alguma sugestão de melhorias?

// Usage
//$.getMultiResources(['script-1.js','style-1.css'], 'assets/somePath/')
//  .done(function () {})
//  .fail(function (error) {})
//  .always(function () {});

(function ($) {
  $.getMultiResources = function (arr, pathOptional, cache) {
    cache = (typeof cache === 'undefined') ? true : cache;
    var _arr = $.map(arr, function (src) {
      var srcpath = (pathOptional || '') + src;
      if (/.css$/i.test(srcpath)) {
        return $.ajax({
          type: 'GET',
          url: srcpath,
          dataType: 'text',
          cache: cache,
          success: function () {
            $('<link>', {
              rel: 'stylesheet',
              type: 'text/css',
              'href': srcpath
            }).appendTo('head');
          }
        });

      } else {
        return $.ajax({
          type: 'GET',
          url: srcpath,
          dataType: 'script',
          cache: cache
        });
      }
    });
    //
    _arr.push($.Deferred(function (deferred) {
      $(deferred.resolve);
    }));
    //
    return $.when.apply($, _arr);
  };
})(jQuery);
django
fonte
0

Você pode tentar usar a recursão. Isso fará o download deles em sincronia, um após o outro, até que o download completo da lista seja concluído.

var queue = ['url/links/go/here'];

ProcessScripts(function() { // All done do what ever you want

}, 0);

function ProcessScripts(cb, index) {
    getScript(queue[index], function() {
        index++;
        if (index === queue.length) { // Reached the end
            cb();
        } else {
            return ProcessScripts(cb, index);
        }
    });
}

function getScript(script, callback) {
    $.getScript(script, function() {
        callback();
    });
}
Rick
fonte