web-dev-qa-db-fra.com

Utiliser getScript de manière synchrone

J'écris un moteur qui nécessite l'utilisation de getScript assez souvent. Je l'ai poussée dans sa propre fonction, pour la facilité d'utilisation, mais je dois maintenant m'assurer que la fonction elle-même est synchrone. Malheureusement, je n'arrive pas à faire attendre à getScript que le chargement du script chargé soit terminé avant de poursuivre. J'ai même essayé de définir la propriété ajax asynch de jQuery sur false avant de passer l'appel. Je pense utiliser le protocole when/done de jQuery, mais je n'arrive pas à comprendre la logique qui consiste à l'insérer dans une fonction et à la rendre synchrone. Toute aide serait très appréciée!

function loadScript(script){
//Unrelated stuff here!!!
$.when(
$.getScript(script,function(){
    //Unrelated stuff here!!!
})).done(function(){
    //Wait until done, then finish function
});
}

Code de boucle (sur demande):

for (var i in divlist){
        switch($("#"+divlist[i]).css({"background-color"})){
            case #FFF:
            loadScript(scriptlist[0],divlist[i]);
            break;
        case #000:
            loadScript(scriptlist[2],divlist[i]);
            break;
        case #333:
            loadScript(scriptlist[3],divlist[i]);
            break;
        case #777:
            loadScript(scriptlist[4],divlist[i]);
            break;
    }
}
20
VashGH

Comme je l'ai dit, il est relativement facile d'enchaîner les appels Ajax avec des objets de promesse. Maintenant, il ne voit pas pourquoi les scripts doivent être chargés les uns après les autres, mais vous en aurez une raison.

Tout d’abord, j’éliminerais l’instruction switch si vous appelez la même fonction avec des arguments différents. Par exemple. vous pouvez mettre toutes les URL de script dans une carte:

var scripts = {
    '#FFF': '...',
    '#000': '...'
    // etc.
};

Vous pouvez enchaîner des promesses en renvoyant simplement une autre promesse d'un rappel passé à .then[docs] . Tout ce que vous avez à faire est de commencer par une promesse ou un objet différé:

var deferred = new $.Deferred();
var promise = deferred.promise();

for (var i in divlist) {
    // we need an immediately invoked function expression to capture
    // the current value of the iteration 
    (function($element) {
        // chaining the promises, 
        // by assigning the new promise to the variable
        // and returning a promise from the callback
        promise = promise.then(function() {
            return loadScript(
                scripts[$element.css("background-color")], 
                $element
            );
        });
    }($('#' + divlist[i])));
}

promise.done(function() {
    // optional: Do something after all scripts have been loaded
});

// Resolve the deferred object and trigger the callbacks
deferred.resolve();

Dans loadScript, vous retournez simplement la promesse retournée de $.getScript ou celle renvoyée par .done:

function loadScript(script_url, $element){
    // Unrelated stuff here!!!

    return $.getScript(script_url).done(function(){
        //  Unrelated stuff here
        // do something with $element after the script loaded.
    });
}

Les scripts seront tous appelés dans l'ordre où ils sont accessibles dans la boucle. Notez que si divlist est un tableau, vous devez utiliser une boucle for normale au lieu d’une boucle for...in.

11
Felix Kling

Cela a fonctionné pour moi et peut vous aider.

$.ajax({
    async: false,
    url: "jui/js/jquery-ui-1.8.20.min.js",
    dataType: "script"
});

Fondamentalement, je viens de contourner la notation abrégée et ajouté dans le async: false

24
Sachseb1

Savez-vous que $.getScriptaccepte une fonction de rappel appelée de manière synchrone après le chargement du script?

Exemple:

$.getScript(url,function(){
//do after loading script
});

J'ai 2 autres solutions: un pur js un et un pour plusieurs js charge .

5
user669677

Essayez de cette façon, créez un tableau avec des objets différés et utilisez $ .when avec "apply"

var scripts = [
    'src/script1.js',
    'src/script2.js'
];

var queue = scripts.map(function(script) {
    return $.getScript(script);
});

$.when.apply(null, queue).done(function() {
    // Wait until done, then finish function
});
3
kupriyanenko
var getScript = function(url) {
    var s = document.createElement('script');
    s.async = true;
    s.src = url;
    var to = document.getElementsByTagName('script')[0];
    to.parentNode.insertBefore(s, to);
};
0
yckart

La réponse de @Felix Kling a été un bon début. Cependant, j'ai découvert qu'il y avait un léger problème avec l'ensemble .done() attaché à la fin du résultat retourné .getScripts() si je voulais le "fonctionnaliser". Vous avez besoin de la dernière promesse des itérations chaînées .getScript() de la boucle. Voici la version modifiée de sa solution (merci, BTW).

Brancher:

(function ($) {
    var fetched = new function () {
            this.scripts = [];
            this.set = [];

            this.exists = function (url) {
                var exists = false;

                $.each(this.set, function (index, value) {
                    if ((url || '') === value) {
                        exists = true;

                        return false;
                    }
                });

                return exists;
            };

            this.buildScriptList = function () {
                var that = this;

                that.set = [];

                $('script').each(function () {
                    var src = $(this).attr('src') || false;

                    if (src) {
                        that.set.Push(src);
                    }
                });

                $.merge(this.set, this.scripts);

                return this;
            };
        },
        getScript = $.getScript;

    $.getScript = function () {
        var url = arguments[0] || '';

        if (fetched.buildScriptList().exists(url)) {
            return $.Deferred().resolve();
        }

        return getScript
            .apply($, arguments)
            .done(function () {
                fetched.scripts.Push(url);
            });
    };

    $.extend({
        getScripts: function (urls, cache) {
            if (typeof urls === 'undefined') {
                throw new Error('Invalid URL(s) given.');
            }

            var deferred = $.Deferred(),
                promise = deferred.promise(),
                last = $.Deferred().resolve();

            if (!$.isArray(urls)) {
                urls = [urls];
            }

            $.each(urls, function (index) {
                promise = promise.then(function () {
                    last = $.getScript(urls[index]);

                    return last;
                });
            });

            if (Boolean(cache || false) && !Boolean($.ajaxSetup().cache || false)) {
                $.ajaxSetup({cache: true});

                promise.done(function () {
                    $.ajaxSetup({cache: false});
                });
            }

            deferred.resolve();

            return last;
        }
    });
})($);

Vous pouvez ignorer la fonction extraite (je l'ai implémentée pour réduire le nombre d'appels redondants potentiels - c'est pourquoi j'ai détourné .getScript()) et voir où la variable last est définie dans la méthode .getScripts(). La valeur par défaut est un objet différé résolu, de sorte que si le tableau urls est vide, il est transmis au résultat renvoyé auquel attacher l'appel .done() extérieur. Sinon, il se verra inévitablement attribuer le dernier objet de promesse issu des appels .getScript() chaînés et veillera ainsi à ce que tout reste synchrone de l'extérieur de la fonction.

Le renvoi de l'objet différé initialement créé ne fonctionnera pas si vous le résolvez avant de le renvoyer à l'invocateur (ce que vous êtes censé faire par documentation officielle de jQuery ).

Exemple:

function loadStuff(data) {
    var version = {
        'accounting': '1.2.3',
        'vue': '1.2.3',
        'vueChart': '1.2.3'
    };

    $.getScripts([
        'https://cdnjs.cloudflare.com/ajax/libs/accounting.js/' + version.accounting + '/accounting.min.js',
        'https://cdnjs.cloudflare.com/ajax/libs/vue/' + version.vue + '/vue.min.js',
        'https://cdnjs.cloudflare.com/ajax/libs/vue-chartjs/' + version.vueChart + '/vue-chartjs.min.js'
    ], true)
        .done(function () {
            // do stuff
        })
        .fail(function () {
            throw new Error('There was a problem loading dependencies.');
        });
}
0
Erutan409