You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

图片加载场景下,如何用设计模式避免晚返回Promise覆盖先完成Promise?

嘿,这个问题在异步加载场景里真的太常见了!尤其是用户快速点击多个缩略图时,慢的请求后完成就会把快的结果直接覆盖掉,导致gallery显示的不是用户最后点击的那张图对吧?我来给你几个实用的解决思路,都是在大型Three.js应用里验证过的:

方法1:用AbortController取消旧请求(推荐)

现代浏览器都支持AbortController,它能真正取消正在进行的图片加载请求,不仅能解决覆盖问题,还能节省带宽(对大型Three.js应用里的高清纹理/图片特别重要)。核心思路是:每次用户点击新缩略图时,先取消上一个还在加载的请求,再发起新请求。

示例代码:

// 维护当前的请求控制器
let currentAbortController = null;

function loadImage(url) {
  // 先取消之前未完成的请求
  if (currentAbortController) {
    currentAbortController.abort();
  }
  // 创建新的控制器
  currentAbortController = new AbortController();
  const signal = currentAbortController.signal;

  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      resolve(img);
      // 请求完成后清空控制器,避免内存泄漏
      currentAbortController = null;
    };
    img.onerror = (err) => {
      reject(err);
      currentAbortController = null;
    };
    // 绑定中止信号到图片加载
    img.src = url;
    signal.addEventListener('abort', () => {
      reject(new Error('请求已被取消'));
      img.src = ''; // 清空图片加载状态
    });
  });
}

// 缩略图点击事件处理
document.querySelectorAll('.thumbnail').forEach(thumb => {
  thumb.addEventListener('click', () => {
    const largeImgUrl = thumb.dataset.largeUrl;
    loadImage(largeImgUrl)
      .then(img => {
        // 更新gallery显示
        const gallery = document.getElementById('gallery');
        gallery.innerHTML = '';
        gallery.appendChild(img);
      })
      .catch(err => {
        // 忽略取消请求的错误,只处理真正的加载失败
        if (err.name !== 'AbortError') {
          console.error('图片加载失败:', err);
        }
      });
  });
});

方法2:用请求ID过滤旧结果(兼容旧浏览器)

如果你的应用需要兼容不支持AbortController的旧浏览器,这个方法更合适。核心思路是:给每个请求分配一个唯一ID,只有当完成的请求ID和当前最新的ID一致时,才更新UI。

示例代码:

// 维护最新的请求ID
let latestRequestId = 0;

function loadImage(url, requestId) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve({ img, requestId });
    img.onerror = reject;
    img.src = url;
  });
}

// 缩略图点击事件处理
document.querySelectorAll('.thumbnail').forEach(thumb => {
  thumb.addEventListener('click', () => {
    // 生成当前请求的唯一ID
    const currentRequestId = ++latestRequestId;
    const largeImgUrl = thumb.dataset.largeUrl;
    
    loadImage(largeImgUrl, currentRequestId)
      .then(({ img, requestId }) => {
        // 只有当前请求是最新的才更新UI
        if (requestId === latestRequestId) {
          const gallery = document.getElementById('gallery');
          gallery.innerHTML = '';
          gallery.appendChild(img);
        }
      })
      .catch(err => console.error('图片加载失败:', err));
  });
});

方法3:封装加载管理器(适合大型应用)

如果你的Three.js应用有大量图片/纹理加载需求,可以封装一个专门的加载管理器,统一处理请求的取消和优先级,让代码更整洁可维护。

示例代码:

class ImageLoadManager {
  constructor() {
    this.currentRequest = null;
  }

  load(url) {
    // 取消上一个请求
    if (this.currentRequest?.controller) {
      this.currentRequest.controller.abort();
    }
    const controller = new AbortController();
    const signal = controller.signal;
    
    const promise = new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => {
        if (!signal.aborted) {
          resolve(img);
        }
      };
      img.onerror = reject;
      img.src = url;
      signal.addEventListener('abort', () => {
        reject(new Error('请求已取消'));
        img.src = '';
      });
    });
    
    this.currentRequest = { controller, promise };
    return promise;
  }
}

// 全局实例化管理器
const imageManager = new ImageLoadManager();

// 缩略图点击事件处理
document.querySelectorAll('.thumbnail').forEach(thumb => {
  thumb.addEventListener('click', () => {
    const largeImgUrl = thumb.dataset.largeUrl;
    imageManager.load(largeImgUrl)
      .then(img => {
        const gallery = document.getElementById('gallery');
        gallery.innerHTML = '';
        gallery.appendChild(img);
      })
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error('图片加载失败:', err);
        }
      });
  });
});

选择建议

  • 优先用方法1,既能解决覆盖问题,又能节省带宽,适合现代浏览器环境下的大型Three.js应用;
  • 如果需要兼容旧浏览器,选方法2,实现简单且兼容性好;
  • 大型应用推荐用方法3,统一管理加载逻辑,便于扩展(比如添加加载状态、错误重试等)。

内容的提问来源于stack exchange,提问作者DTC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:16:51