为何使用await的async图片加载函数拖慢了批量图片加载速度?
问题:批量图片加载的耗时差异疑问
我编写了如下图片加载函数:
export function loadImage(url: string): Promise<HTMLImageElement> { return new Promise((resolve, reject) => { if (image_map.has(url)) { resolve(image_map.get(url)); } else { let image: HTMLImageElement = new Image(); if (typeof window.static_url != 'undefined' && url[0] == '/') { image.src = `${window.static_url}${url}`; } else { image.src = url; } image_map.set(url, image); image.crossOrigin = 'Anonymous'; // Prevent canvas getImageData CORS issue image.onload = function () { resolve(image); }; image.onerror = reject; } }); }
我需要加载约200张图片,此前使用如下代码加载:
for (let url of image_list) { await loadImage(url); }
加载耗时极长,达到16秒甚至更久。
今天我移除了代码中的await,图片加载仅用了2秒完成。这是为什么?我以为JavaScript是单线程的,原本以为移除await只是让图片加载在其余代码之后执行,但总耗时不该有如此大的差异。
解答
核心原因是串行加载 vs 并行加载的区别,和JS单线程的特性并不冲突:
- JS主线程确实是单线程,但浏览器的网络请求是由专门的网络线程处理的,JS只负责发起请求,之后等待回调通知结果。
- 加
await的循环是串行加载:每一次循环都会等待当前图片的loadImagePromise resolve(也就是图片加载完成),才会执行下一次循环、发起下一张图片的请求。200张图片就会排队依次加载,总耗时是所有图片加载时间的总和,自然会很长。 - 移除
await后是并行加载:循环会一次性遍历所有图片URL,调用loadImage时立刻创建Image实例、设置src,浏览器会同时发起多个网络请求(浏览器对同域名有并发数限制,一般是6-8个,剩下的会在已有请求完成后排队,但比起串行效率高太多)。总耗时基本由加载最慢的那几张图片决定,所以能从16秒大幅降到2秒。
你之前的误解在于:移除await不是让加载在其他代码之后执行,而是JS主线程不会等待每一张图片加载完成,而是一次性把所有请求都发出去,让浏览器并行处理这些网络请求,自然总耗时会大幅缩短。
内容的提问来源于stack exchange,提问作者Shaobin Jiang
相关产品推荐
相关产品推荐

