批量检测无效图书封面时XHR请求返回Blob为null的问题求助
问题分析与优化方案
核心问题根源
你遇到的Blob返回null和无效封面标记失效问题,主要来自两个关键原因:
- 闭包陷阱:循环里用
var声明的imgInt是函数级作用域,当异步onload回调执行时,循环已经跑完,imgInt会固定指向数组最后一个元素的索引,导致判断逻辑完全混乱。 - 并发请求过载:一次性发起大量XHR请求,浏览器会限制并发数(通常Chrome是6个左右),超出的请求会被阻塞甚至直接失败,最终返回
null的Blob。
另外,代码里没有处理请求失败的情况,一旦请求出错也会导致blob为null,触发控制台报错。
优化方案分步实现
1. 基础修复:解决闭包陷阱与请求报错
把循环里的var换成let,利用块级作用域让每个循环迭代保留独立变量引用,同时增加错误处理:
/* 填入已知无效图书封面的文件大小、高度和宽度:子数组顺序为[字节数, 高度, 宽度] */ var noBookCoverSizes = [[2724,100,80],[2727,100,80],[154,120,80]]; jQuery(document).ready(function(){ /* 确认当前页面为目标页面 */ if (jQuery('.portlet-queryRecordSearchResult').length && !jQuery('.portlet-catalogueDetail').length) { /* 遍历每个图书封面 */ jQuery('.arena-record-cover a img').each(function(){ const img = jQuery(this); const imgUrl = img.attr('src'); const imgHeight = img[0].naturalHeight; const imgWidth = img[0].naturalWidth; /* 遍历无效封面参数数组,用let创建块级作用域 */ for (let i = 0; i < noBookCoverSizes.length; i++) { /* 检查宽高是否匹配 */ if(noBookCoverSizes[i][1] === imgHeight && noBookCoverSizes[i][2] === imgWidth) { const targetSize = noBookCoverSizes[i][0]; const xhr = new XMLHttpRequest(); /* 发起GET请求 */ xhr.open('GET', imgUrl, true); xhr.responseType = 'blob'; // 增加请求失败处理 xhr.onerror = function() { console.warn(`请求封面失败: ${imgUrl}`); }; xhr.onload = function() { const blob = this.response; /* 先判断blob存在,再检查文件大小 */ if(blob && blob.size === targetSize) { /* 用closest替代多层parent,结构变化也不会失效 */ img.closest('.arena-record-cover').addClass('no-book-cover'); } } xhr.send(); } } }); } });
2. 进阶优化:控制并发请求数量(彻底解决过载问题)
如果页面封面数量较多,还是会触发浏览器并发限制,我们可以用请求队列来控制同时发起的请求数:
/* 填入已知无效图书封面的文件大小、高度和宽度:子数组顺序为[字节数, 高度, 宽度] */ const noBookCoverSizes = [[2724,100,80],[2727,100,80],[154,120,80]]; // 并发请求上限,建议设为5-6,匹配浏览器默认限制 const MAX_CONCURRENT = 5; // 请求队列与活跃请求计数 let requestQueue = []; let activeRequests = 0; // 封装检查无效封面的异步函数 function checkInvalidCover(img, imgUrl, imgHeight, imgWidth) { return new Promise((resolve) => { // 先过滤匹配宽高的无效尺寸 const matchedSizes = noBookCoverSizes.filter(size => size[1] === imgHeight && size[2] === imgWidth); if (matchedSizes.length === 0) { resolve(false); return; } const xhr = new XMLHttpRequest(); xhr.open('GET', imgUrl, true); xhr.responseType = 'blob'; xhr.onload = function() { const blob = this.response; const isInvalid = blob && matchedSizes.some(size => size[0] === blob.size); if (isInvalid) { img.closest('.arena-record-cover').addClass('no-book-cover'); } resolve(isInvalid); }; xhr.onerror = function() { console.warn(`请求封面失败: ${imgUrl}`); resolve(false); }; xhr.send(); }); } // 处理请求队列的函数 function processQueue() { while (activeRequests < MAX_CONCURRENT && requestQueue.length > 0) { const task = requestQueue.shift(); activeRequests++; task().then(() => { activeRequests--; processQueue(); }); } } jQuery(document).ready(function(){ /* 确认当前页面为目标页面 */ if (jQuery('.portlet-queryRecordSearchResult').length && !jQuery('.portlet-catalogueDetail').length) { /* 遍历每个图书封面,加入请求队列 */ jQuery('.arena-record-cover a img').each(function(){ const img = jQuery(this); const imgUrl = img.attr('src'); const imgHeight = img[0].naturalHeight; const imgWidth = img[0].naturalWidth; requestQueue.push(() => checkInvalidCover(img, imgUrl, imgHeight, imgWidth)); }); // 启动队列处理 processQueue(); } });
3. 额外优化:缓存请求结果(避免重复请求)
如果多个封面使用同一个无效图片URL,我们可以缓存请求结果,减少重复请求:
// 新增URL-文件大小缓存对象 const urlSizeCache = new Map(); // 修改checkInvalidCover函数,加入缓存逻辑 function checkInvalidCover(img, imgUrl, imgHeight, imgWidth) { return new Promise((resolve) => { const matchedSizes = noBookCoverSizes.filter(size => size[1] === imgHeight && size[2] === imgWidth); if (matchedSizes.length === 0) { resolve(false); return; } // 先检查缓存 if (urlSizeCache.has(imgUrl)) { const size = urlSizeCache.get(imgUrl); const isInvalid = matchedSizes.some(item => item[0] === size); if (isInvalid) { img.closest('.arena-record-cover').addClass('no-book-cover'); } resolve(isInvalid); return; } // 无缓存时发起请求 const xhr = new XMLHttpRequest(); xhr.open('GET', imgUrl, true); xhr.responseType = 'blob'; xhr.onload = function() { const blob = this.response; const size = blob ? blob.size : -1; urlSizeCache.set(imgUrl, size); // 存入缓存 const isInvalid = blob && matchedSizes.some(item => item[0] === size); if (isInvalid) { img.closest('.arena-record-cover').addClass('no-book-cover'); } resolve(isInvalid); }; xhr.onerror = function() { console.warn(`请求封面失败: ${imgUrl}`); urlSizeCache.set(imgUrl, -1); // 标记请求失败 resolve(false); }; xhr.send(); }); }
测试验证
先测试基础修复版代码,应该能解决添加第三个无效尺寸后标记失效的问题;如果页面封面数量较多,再使用带并发控制的版本,就能彻底解决Blob返回null的问题。
内容的提问来源于stack exchange,提问作者JoakimB
相关产品推荐
相关产品推荐

