异步脚本加载回调函数

32

我创建了一个短函数,基于Mathias Bynens优化的Google Analytics异步脚本,如下所示:

http://jsfiddle.net/JamesKyle/HQDu6/

function async(src) {
  var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
  o.src = '//' + src;
  s.parentNode.insertBefore(o, s);
}

这个很好用,我已经开始在几个不同的脚本中使用它了。

// Crazy Egg
async('dnn506yrbagrg.cloudfront.net/pages/scripts/XXXXX/XXXXX.js?' + Math.floor(new Date().getTime() / 3600000));

// User Voice
var uvOptions = {};
async('widget.uservoice.com/XXXXX.js');

// Google Analytics
var _gaq = [['_setAccount', 'UA-XXXXX-XX'], ['_setDomainName', 'coachup.com'], ['_trackPageview']];
async('google-analytics.com/ga.js');

// Stripe
async('js.stripe.com/v1');​

问题出现在我遇到需要在加载后调用的脚本时:
// Snap Engage
async('snapabug.appspot.com/snapabug.js');
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');

所以我想把这个变成一个回调函数,使用方式如下:

async('snapabug.appspot.com/snapabug.js', function() {
    SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});

我没想到这对我来说会很难,但事实就是如此。

我的问题是,什么是最有效的添加回调函数的方法,而不会使代码过于复杂。

请参见 jsfiddle:http://jsfiddle.net/JamesKyle/HQDu6/


1
https://dev59.com/P3A75IYBdhLWcg3w7tt6#3211647:你可以使用Python的os模块来检查文件是否存在。这是一个例子:import os.path if os.path.isfile(filename): print("File exists") else: print("File does not exist")https://dev59.com/pG855IYBdhLWcg3wm1m3#4249346:您可以使用Python的subprocess模块来运行外部命令。这是一个例子:import subprocess subprocess.call(["ls", "-l"])这将运行“ls -l”命令并打印输出。 - RASG
3个回答

58

感谢RASG提供的https://dev59.com/P3A75IYBdhLWcg3w7tt6#3211647

带回调函数的异步函数:

function async(u, c) {
  var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
  o.src = '//' + u;
  if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
  s.parentNode.insertBefore(o, s);
}

使用方法:

async('snapabug.appspot.com/snapabug.js', function() {
    SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});

jsFiddle


1
o.u = '//' + u;应该写成o.src ='//' + u;否则它将不会加载任何字节。压缩有时可能很棘手。 - aefxx
2
s.addEventListener 应改为 o.addEventListener。 - baohouse
1
我认为这需要更多的逻辑来处理IE8及其之前的版本,例如:http://www.aaronpeters.nl/blog/prevent-double-callback-execution-in-IE9。 - Dave Cahill
我认为这个解决方案更全面,而且还包括了一个 Promise 实现:https://dev59.com/Auo6XIcBkEYKwwoYSCg1 - Kevin Farrugia

14

一个更近期的片段:

async function loadAsync(src) {
    const script = document.createElement('script');
    script.src = src;
    return new Promise((resolve, reject) => {
        script.onreadystatechange = function () {
            if (script.readyState === 'loaded' || script.readyState === 'complete') {
                script.onreadystatechange = null;
                resolve(true);
            }
        };
        document.getElementsByTagName('head')[0].appendChild(script);
    });
}

使用

  loadAsync(`https://....js`).then(_ => {
    //  ... script loaded here
  })

James Kyle的回答没有考虑IE9。这是我在评论中提供的链接中找到的修改版代码。修改变量baseUrl以便相应地找到脚本。

//for requiring a script loaded asynchronously.
function loadAsync(src, callback, relative){
    var baseUrl = "/resources/script/";
    var script = document.createElement('script');
    if(relative === true){
        script.src = baseUrl + src;  
    }else{
        script.src = src; 
    }

    if(callback !== null){
        if (script.readyState) { // IE, incl. IE9
            script.onreadystatechange = function() {
                if (script.readyState === "loaded" || script.readyState === "complete") {
                    script.onreadystatechange = null;
                    callback();
                }
            };
        } else {
            script.onload = function() { // Other browsers
                callback();
            };
        }
    }
    document.getElementsByTagName('head')[0].appendChild(script);
}

使用:

loadAsync('https://www.gstatic.com/charts/loader.js' , function(){
    chart.loadCharts();
    });
// OR relative path
loadAsync('fastclick.js', null, true);

干得好,感谢你的出色回答。只需将函数中最后一行的“doc”更改为“document”。 - Trev14

12

其他回答也可以,但不易读或需要使用Promise。这是我的两分钱:

function loadScript(src, callback) {
    var script = document.createElement('script');
    script.setAttribute('src', src);
    script.addEventListener('load', callback);
    document.head.appendChild(script);
},

1
小改进:如果您使用 script.addEventListener('load', callback,{ once: true });,则侦听器将在加载事件触发后自动清除。 - Humppakäräjät

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接