jQuery中如何处理并行脚本加载时相同资源的等待问题
问题解决方案
问题根因
原实现仅使用window.loadedScripts数组记录已加入加载队列的脚本地址,没有区分「加载中」和「已加载完成」两种状态,也没有存储对应脚本的加载等待对象,导致并发请求同一个未完成加载的脚本时,后续请求会直接判定为已加载,提前触发回调,出现依赖未加载完成的异常。
解决思路
通过全局Map存储每个脚本对应的加载Deferred对象,所有请求同一脚本的任务都会复用同一个Deferred等待加载结果,仅第一次请求会发起真实加载,加载完成后统一触发所有等待该脚本的任务回调。
完整修改后代码
// Function to load Scripts on the fly $.loadScript = function(url, arg1, arg2) { var cache = false, callback = null; //arg1 and arg2 can be interchangable if ($.isFunction(arg1)) { callback = arg1; cache = arg2 || cache; } else { cache = arg1 || cache; callback = arg2 || callback; } var that = this; // 初始化全局脚本加载状态存储,key为脚本url,value为对应加载Deferred window.scriptLoadMap = window.scriptLoadMap || new Map(); // 保留原全局数组兼容其他可能的依赖,无需兼容可删除 window.loadedScripts = window.loadedScripts || []; var deferred = jQuery.Deferred(); if (jQuery.isFunction(callback)) { deferred.done(function(){ callback.call(that); }); }; if( url.constructor === Array ){ function loadScript(i) { if (i < url.length) { var el = url[i]; var existDeferred = window.scriptLoadMap.get(el); // 已有加载任务,直接等待完成后加载下一个 if (existDeferred) { existDeferred.done(() => loadScript(i + 1)); return; } // 第一次加载,创建全局Deferred存储 var scriptDeferred = jQuery.Deferred(); window.scriptLoadMap.set(el, scriptDeferred); // 发起加载请求 $.ajax({ url: el, success: function(e){ __info('Loaded script. url: '+el, 'verbose'); window.loadedScripts.push(el); // 标记脚本加载完成 scriptDeferred.resolve(); loadScript(i + 1); }, complete: function(e){ if(typeof e.done !== 'function'){ $(function () { $('<script>') .attr('type', 'text/javascript') .text(e.responseText) .prop('defer',true) .appendTo('head'); }) } }, dataType: 'script', cache: cache }); } else { deferred.resolve(); } } loadScript(0); return deferred; } else { var existDeferred = window.scriptLoadMap.get(url); // 已有加载任务,等待完成后resolve当前请求 if (existDeferred) { existDeferred.done(() => deferred.resolve()); return deferred; } // 第一次加载,创建全局Deferred存储 var scriptDeferred = jQuery.Deferred(); window.scriptLoadMap.set(url, scriptDeferred); $.ajax({ url: url, success: function(e){ __info('Loaded script. url: '+url, 'verbose'); window.loadedScripts.push(url); // 标记脚本加载完成 scriptDeferred.resolve(); deferred.resolve(); }, complete: function(e){ if(typeof e.done !== 'function'){ $(function () { $('<script>') .attr('type', 'text/javascript') .text(e.responseText) .prop('defer',true) .appendTo('head'); }) } }, dataType: 'script', cache: cache }); } return deferred; };
核心修改说明
- 新增全局
scriptLoadMap存储结构,用于记录每个脚本url对应的加载Deferred对象,区分「加载中」和「已完成」状态 - 单脚本/批量脚本加载时,优先查询全局存储:如果已有对应脚本的加载Deferred,直接绑定当前请求的后续逻辑到该Deferred的完成回调上,无需重复发起请求,也不会提前结束
- 仅第一次请求某脚本时会发起真实AJAX加载,加载完成后触发所有等待该脚本的请求的后续逻辑
- 保留原有
loadedScripts数组、缓存逻辑、脚本注入逻辑,完全兼容原有使用方式
内容的提问来源于stack exchange,提问作者Fran Díaz
相关产品推荐
相关产品推荐

