Cómo incluir múltiples archivos js usando el método jQuery $ .getScript ()


127

Estoy tratando de incluir dinámicamente archivos javascript en mi archivo js. Investigué un poco al respecto y descubrí que el método jQuery $ .getScript () sería la forma deseada.

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

Pero me pregunto si este método puede cargar varios scripts a la vez. Por qué pregunto esto es porque a veces mi archivo javascript depende de más de un archivo js.

Gracias de antemano.

Respuestas:


315

La respuesta es

Puede usar promesas con getScript()y esperar hasta que se carguen todos los scripts, 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
    
});

VIOLÍN

OTRA FIDDLE

En el código anterior, agregar un diferido y resolverlo dentro $()es como colocar cualquier otra función dentro de una llamada jQuery, como $(func), es lo mismo que

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

es decir, espera que el DOM esté listo, por lo que en el ejemplo anterior $.whenespera que se carguen todos los scripts y que el DOM esté listo debido a la $.Deferredllamada que se resuelve en la devolución de llamada lista para DOM.


Para un uso más genérico, una función práctica

Se podría crear una función de utilidad que acepte cualquier conjunto de scripts de esta manera:

$.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 se puede usar así

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

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

donde la ruta se antepondrá a todos los scripts, y también es opcional, lo que significa que si la matriz contiene la URL completa, también se podría hacer esto, y dejar de lado la ruta todos juntos

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

Argumentos, errores, etc.

Como comentario adicional, tenga en cuenta que la donedevolución de llamada contendrá una serie de argumentos que coinciden con los pasados ​​en los scripts, cada argumento representa una matriz que contiene la respuesta

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

donde cada matriz contendrá algo así [content_of_file_loaded, status, xhr_object]. Por lo general, no necesitamos acceder a esos argumentos ya que los scripts se cargarán automáticamente de todos modos, y la mayoría de las veces la donedevolución de llamada es todo lo que realmente buscamos para saber que todos los scripts se han cargado, solo lo agrego para completarlo , y para las raras ocasiones en que se necesita acceder al texto real del archivo cargado, o cuando se necesita acceso a cada objeto XHR o algo similar.

Además, si alguno de los scripts no se carga, se llamará al manejador de errores y no se cargarán los scripts posteriores.

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

11
Gracias por una gran respuesta ¿Te importaría explicarme por qué agregar $.Deferred(function( deferred ){$( deferred.resolve );})aquí?
Sozhen

11
El objeto diferido realmente no tiene nada que ver con la promesa que le permite cargar varias secuencias de comandos y realizar una función cuando todas estén listas. Simplemente verifica si $ () está listo, y resuelve si lo está, en otras palabras, verifica que el DOM esté listo antes de ejecutar cualquier código, al igual que document.ready, y encontré un buen ejemplo de adjuntar promete obtener Script en línea que tenía la funcionalidad diferida lista para DOM en el código, y simplemente decidió mantenerlo, ya que parecía una buena idea.
adeneo

44
Esto se debe a que el almacenamiento en caché en ajax está desactivado de forma predeterminada en jQuery, para activarlo y eliminar la cadena de consulta do: $.ajaxSetup({ cache: true });pero eso también podría afectar otras llamadas ajax que no desea almacenar en caché, hay mucho más sobre esto en los documentos para getScript , e incluso hay un pequeño tutorial sobre cómo crear una función getScript en caché llamada cachedScript.
adeneo

3
Perdón por la tardía aceptación. A veces tu camino aún no funciona del todo. No estoy seguro de cuál es la razón. Estoy tratando de incorporar cuatro archivos de script a la vez. $.when($.getScript(scriptSrc_1), $.getScript(scriptSrc_2), $.getScript(scriptSrc_3), $.getScript(scriptSrc_4), $.Deferred(function(deferred) { $(deferred.resolve); })).done(function() { ... })
sozhen

1
Para cualquiera que no pueda hacer que esto funcione, asegúrese de que no haya errores de análisis en sus archivos JS, es decir, compruebe que se cargan correctamente en una etiqueta <script> primero. jQuery.getScript () considerará estos errores como una carga fallida y se llamará a .fail () en lugar de .done (). @SongtaoZ.
poder del prisma lunar

29

Implementé una función simple para cargar múltiples scripts en paralelo:

Función

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

1
Por alguna razón, este funcionó mejor que el de Emanuel. Es una implementación muy transparente también.
Todd

@satnam gracias :)! la respuesta principal no funciona para algunas personas (incluido yo), así que publiqué esta solución. Y se ve mucho más simple.
Andrei

10

Cargue el siguiente script necesario en la devolución de llamada del anterior como:

$.getScript('scripta.js', function()
{
   $.getScript('scriptb.js', function()
   {
       // run script that depends on scripta.js and scriptb.js
   });
});

1
¿Seguramente este método descargará los scripts secuencialmente, ralentizando el tiempo hasta la ejecución final?
Jimbo

1
Correcto, @Jimbo, para ser justos; Es preferible a la ejecución prematura. :)
Alastair

Las cascadas casi siempre deben evitarse. getScript viene con una promesa ... Puede usar $. cuando escucha cuando las promesas se resuelven.
Roi

@Roi y cualquier otra persona: si NECESITO que los scripts se carguen en un ORDEN ESPECÍFICO, ¿qué, fundamentalmente hablando, está mal con este enfoque? ¿Y qué ventaja, por lo tanto, trae la alternativa de promesa?
Ifedi Okonkwo

@IfediOkonkwo, esto funciona cuando necesitan estar en serie, pero el operador original acaba de mencionar que le gustaría múltiples de una vez (en el cual, esto sería un antipatrón). Aun así, si los quisiera en serie, escribiría una función de bucle a la que podría pasar una matriz. La anidación profunda se vuelve desordenada rápidamente.
Roi

9

A veces es necesario cargar scripts en un orden específico. Por ejemplo, jQuery debe cargarse antes de jQuery UI. La mayoría de los ejemplos en esta página cargan scripts en paralelo (asincrónicamente), lo que significa que no se garantiza el orden de ejecución. Sin ordenar, el script yque depende xpodría romperse si ambos se cargan correctamente pero en un orden incorrecto.

Propongo un enfoque híbrido que permite la carga secuencial de scripts dependientes + carga paralela opcional + objetos diferidos :

/*
 * 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>

Los scripts en ambas colas se descargarán en paralelo, sin embargo, los scripts en cada cola se descargarán en secuencia, asegurando la ejecución ordenada. Gráfico de cascada:

tabla de scripts de cascada


¿No necesitamos agregar la etiqueta del script en HTML una vez que el script se carga en la memoria?
Ankush Jain

4

Use yepnope.js o Modernizr (que incluye yepnope.js as Modernizr.load).

ACTUALIZAR

Solo para seguir, aquí hay un buen equivalente de lo que tiene actualmente usando yepnope, que muestra dependencias en múltiples scripts:

yepnope({
  load: ['script1.js', 'script2.js', 'script3.js'],
  complete: function () {
      // all the scripts have loaded, do whatever you want here
  }
});

Gracias. ¿Puedo usar múltiples yepnope.injectJs( scriptSource )al comienzo de mi archivo javascript al igual que incluir <script>etiquetas en el archivo html?
sozhen

Eso es nuevo y, sinceramente, no entiendo por qué es necesario. Siga la documentación un poco más allá de la lista de cambios recientes y verá el uso más convencional.
Chris Pratt

+1; Vale la pena señalar que esto gestiona adecuadamente las dependencias (es decir, no cargará el mismo script dos veces) a diferencia $.getScript. Este es un gran problema.
OV

yepnope.js ahora está en desuso .
Stephan

4

Me encontré con una serie de problemas con la carga de múltiples scripts, incluido un problema con (al menos en Chrome) la carga en caliente del mismo dominio de scripts que no se ejecutan realmente después de ser cargados con éxito por Ajax, donde Cross Domain funciona perfectamente. :(

La respuesta seleccionada a la pregunta original no funciona de manera confiable.

Después de muchas iteraciones, aquí está mi respuesta final a getScript (s) y cargar múltiples scripts asincrónicamente en un orden estricto específico con la opción de devolución de llamada cargada por script y la devolución de llamada general al finalizar, Probado en jQuery 2.1+ y versiones modernas de Chrome, Firefox más el Internet Explorer abandonado.

Mi caso de prueba fue cargar archivos para un render de webGL THREE.JS y luego comenzar el script de renderizado cuando THREE global estuvo disponible utilizando una comprobación de intervalo que se pasó a una llamada de función anónima a onComplete.

La función 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();
};

Cómo utilizar

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

La función es como es porque descubrí que uso Diferido (¿Desaprobado ahora?), ¿Cuándo, Éxito y Completo en mis pruebas NO es 100% confiable? ?, De ahí esta función y el uso de statusCode, por ejemplo.

Es posible que desee agregar un comportamiento de manejo de errores / fallas si lo desea.


¿+1 para cargarlos en el orden correcto, a menudo esencial, y algo que la respuesta aceptada no garantizará a menos que me falte algo? He publicado una respuesta más corta similar a continuación, pero este es un caso más general.
Whelkaholism

2

Puede utilizar el $.whenmétodo -probando la siguiente función:

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

Esta función se usaría así:

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

Esa es la forma de usar Promesas y tener un mínimo de gastos generales.


¿Esto garantiza que los scripts se carguen en secuencia?
CyberMonk

2

Gran respuesta, adeneo.

Me tomó un tiempo descubrir cómo hacer que su respuesta sea más genérica (para poder cargar una serie de scripts definidos por código). La devolución de llamada se llama cuando todos los scripts se han cargado y ejecutado. Aquí está mi solución:

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

2

Agregar scripts con async = false

Aquí hay un enfoque diferente, pero súper simple. Para cargar varios scripts, simplemente puede agregarlos al cuerpo.

  • Los carga de forma asincrónica, porque así es como los navegadores optimizan la carga de la página
  • Ejecuta scripts en orden, porque así es como los navegadores analizan las etiquetas HTML
  • No hay necesidad de devolución de llamada, porque los scripts se ejecutan en orden. Simplemente agregue otro script, y se ejecutará después de los otros scripts

Más información aquí: 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);
});

1

Lo que está buscando es un cargador compatible con AMD (como require.js).

http://requirejs.org/

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

Hay muchos buenos de código abierto si lo buscas. Básicamente, esto le permite definir un módulo de código, y si depende de otros módulos de código, esperará hasta que esos módulos hayan finalizado la descarga antes de proceder a ejecutarse. De esta forma, puede cargar 10 módulos de forma asincrónica y no debería haber problemas, incluso si uno depende de algunos de los otros para ejecutar.


1

Esta función asegurará que se cargue un archivo después de que el archivo de dependencia se cargue por completo. Solo necesita proporcionar los archivos en una secuencia teniendo en cuenta las dependencias de otros archivos.

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

Quizás mejor que la respuesta aceptada ya que carga los archivos en orden. Si script2 requiere que se cargue script1, entonces es esencial ordenar.
Salman A

1

Esto funciona para mi:

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

Y úsalo:

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

1

Aquí está la respuesta usando la de Maciej Sawicki e implementando Promisecomo devolución de llamada:

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

Utilizar:

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

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

Todos los archivos Javascript se descargan lo antes posible y se ejecutan en el orden indicado. Entonces taskNeedingParametersse llama.


0

Versión más corta de la respuesta integral de Andrew Marc Newton anterior. Este no verifica el código de estado para el éxito, lo que debe hacer para evitar un comportamiento de UI indefinido.

Este era para un sistema molesto en el que podía garantizar jQuery pero ningún otro incluye, por lo que quería una técnica lo suficientemente corta como para que no se convierta en un script externo si se lo obliga a hacerlo. (Puede hacerlo aún más corto pasando el índice 0 a la primera llamada "recursiva", pero la fuerza de los hábitos de estilo me hizo agregar el azúcar).

También estoy asignando la lista de dependencias a un nombre de módulo, por lo que este bloque se puede incluir en cualquier lugar donde necesite "módulo1" y los scripts y la inicialización dependiente solo se incluirán / ejecutarán una vez (puede iniciar sesión indexen la devolución de llamada y ver un pedido único conjunto de solicitudes AJAX en ejecución)

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

0

Hay un complemento que extiende el método getScript de jQuery. Permite la carga asíncrona y sincrónica y utiliza el mecanismo de almacenamiento en caché de jQuery. Revelación completa, escribí esto. Siéntase libre de contribuir si encuentra un método mejor.

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


0

Carga n scripts uno por uno (útil si, por ejemplo, el segundo archivo necesita el primero):

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

0

basado en la respuesta de @adeneo arriba: Combinando la carga de archivos css y js

¿Alguna sugerencia para mejorar?

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

0

Puedes probar esto usando la recursividad. Esto los descargará sincronizados, uno tras otro hasta que se complete la descarga de la lista completa.

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();
    });
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.