异步脚本加载回调

Posted

技术标签:

【中文标题】异步脚本加载回调【英文标题】:Asynchronous Script Loading Callback 【发布时间】:2012-10-01 00:27:48 【问题描述】:

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

我创建了一个基于Mathias Bynens Optimization of the Google Analytics asynchronous script 的简短函数,如下所示:

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/

【问题讨论】:

***.com/a/3211647/982924 和 ***.com/a/4249346/982924 【参考方案1】:

感谢RASGhttps://***.com/a/3211647/982924

带回调的异步函数:

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

【讨论】:

o.u = '//' + u; 行应为o.src = '//' + u;,否则不会加载单个字节。缩小有时会很棘手。 s.addEventListener 应该是 o.addEventListener 我认为这需要更多的逻辑来处理 IE8 及之前的版本,例如:aaronpeters.nl/blog/prevent-double-callback-execution-in-IE9 我认为这个解决方案更全面,也包括Promise 实现:***.com/questions/7718935/load-scripts-asynchronously 可怕的变量名【参考方案2】:

一个更新的sn-p:

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's answer 不考虑 IE9。这是我找到的代码的修改版本in the link proposed in the comments。修改 var 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”即可。【参考方案3】:

其他答案效果很好,但不是超级可读或需要承诺。这是我的两分钱:

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

【讨论】:

以上是关于异步脚本加载回调的主要内容,如果未能解决你的问题,请参考以下文章

如果在异步加载 gmap 时未指定回调,则不会加载其他必要的谷歌地图脚本

javascript脚本如何异步加载,有啥作用

Node.js 的回调模式

JavaScript 回调/异步问题? Chrome 扩展程序的后台脚本在完成之前停止功能

require.js详解

JavaScript异步编程 异步的脚本加载