返回

解决 window.showDirectoryPicker() 文件选择器已激活错误

javascript

解决 "window.showDirectoryPicker() not allowed: File picker already active" 错误

window.showDirectoryPicker() API 允许用户选择一个目录,并为 Web 应用程序提供对该目录下文件的访问权限。当多次快速连续触发目录选择器,或者前一个目录选择器未正常关闭时,就可能遇到 “window.showDirectoryPicker() not allowed: File picker already active” 错误。 这个问题会阻止用户正常打开目录选择器,影响 Web 应用的功能。 本文将分析此问题的原因,并提供几种解决方案。

问题分析

出现 “file picker already active” 错误的核心原因是:浏览器同时只允许存在一个活跃的文件或目录选择器。 以下情况可能触发此错误:

  • 用户操作过快: 用户快速点击触发目录选择器的按钮,在前一个选择器窗口尚未关闭时,又尝试打开一个新的选择器。
  • 异步操作导致的问题: 在异步操作中,例如 Promise 链中,如果在前一个 showDirectoryPicker() 操作尚未完成时,就再次调用它。
  • 错误处理不完善:showDirectoryPicker() 抛出异常后,没有正确处理选择器的状态,导致选择器仍然被认为是激活状态。

解决方案

以下提供几种解决此问题的方案:

1. 增加防抖 (Debounce) 机制

防抖是一种控制函数执行频率的技术。 通过防抖,可以确保在用户连续点击按钮时,只有最后一次点击才会真正触发 showDirectoryPicker() 方法。 这可以有效防止因用户操作过快而导致的错误。

代码示例:

const btn = document.getElementById('btn');
const pre = document.getElementById('pre');
let filePickerActive = false; // 添加一个变量跟踪状态

function debounce(func, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

const debouncedShowPicker = debounce(async () => {

  if (filePickerActive) return; //如果已经是激活状态就直接返回

  filePickerActive = true;
  try {
      const dirHandle = await window.showDirectoryPicker();
      pre.innerHTML = `${dirHandle.name}`;
  } 
  catch (error) {       
      console.log(error);      
  } finally{
    filePickerActive = false; // 确保重置状态
  }
}, 500); // 500ms 防抖延迟

btn.addEventListener('click', debouncedShowPicker);

操作步骤:

  1. 将以上代码复制到你的 JavaScript 文件中。
  2. debounce 函数接收一个需要防抖的函数和延迟时间(以毫秒为单位)作为参数,返回一个新的防抖函数。
  3. 使用 debouncedShowPicker 替代原来的 showDirectoryPicker 调用,并将其绑定到按钮的点击事件。
  4. 调整延迟时间以适应你的应用场景,一般设置为 200-500ms 即可。

2. 使用状态标记管理选择器状态

使用一个布尔变量来跟踪 showDirectoryPicker() 的状态,确保在调用 showDirectoryPicker() 之前,上一个选择器已经关闭或完成。

代码示例:

const btn = document.getElementById('btn');
const pre = document.getElementById('pre');
let filePickerActive = false; // 状态标记

btn.addEventListener('click', async (e) => {
  if (filePickerActive) return;  // 如果选择器已激活,则直接返回

  filePickerActive = true; // 设置状态为激活

  try {
      const dirHandle = await window.showDirectoryPicker();
      pre.innerHTML = `${dirHandle.name}`;
  }
  catch (error) {
      console.error("Error showing directory picker:", error);
  }
  finally {
      filePickerActive = false; // 完成后重置状态
  }
});

操作步骤:

  1. 声明一个名为 filePickerActive 的变量,并将其初始化为 false
  2. click 事件处理函数中,首先检查 filePickerActive 的值。如果为 true,则直接返回,不执行 showDirectoryPicker()
  3. 在调用 showDirectoryPicker() 之前,将 filePickerActive 设置为 true
  4. try...catch...finally 块的 finally 子句中,将 filePickerActive 设置为 false,确保状态得到正确重置。 这保证了无论操作成功还是失败,状态都会被重置。

3. 检查并重置异常状态

某些情况下, showDirectoryPicker() 可能会因为用户取消操作或系统错误而抛出异常。 这时,需要在错误处理逻辑中手动重置选择器状态,以防止后续调用失败。

代码示例:

const btn = document.getElementById('btn');
const pre = document.getElementById('pre');
let filePickerActive = false;

btn.addEventListener('click', async (e) => {
  if (filePickerActive) return;
  filePickerActive = true;
  try {
    const dirHandle = await window.showDirectoryPicker();
    pre.innerHTML = `${dirHandle.name}`;
  }
  catch (error) {
    console.error("Error showing directory picker:", error);
    //  处理 DOMException 或其他特定错误类型
    if (error.name === 'AbortError' || error.message.includes("file picker canceled") || error instanceof DOMException && error.code === 8){
      // 用户取消操作,不用做处理, 因为filePickerActive已经被finally设置为false
      console.log("用户取消了目录选择")
    }
    else{
      // 出现了意料之外的问题,有必要的话可以考虑提示用户重试或者刷新页面
      console.error("出现其他错误,需要进行更详细的处理")
      filePickerActive = false;  // 其他类型的异常,尝试重置
    }
  } finally {
      // 如果不是明确的用户取消类型,不强制重置
    if(filePickerActive){
       filePickerActive = false; // 完成后重置状态
    }

  }
});

操作步骤:

  1. catch 块中捕获 showDirectoryPicker() 抛出的异常。
  2. 判断异常的类型或者包含的特定信息 (比如 "file picker canceled")。如果是用户主动取消,就不用处理,否则执行重置状态的代码,将 filePickerActive 设置为 false
  3. 针对更复杂的应用,可能需要记录日志或向用户显示错误信息。

安全建议

  • 最小权限原则: 请求访问目录时,只请求必要的权限。 例如,如果只需要读取文件,则不要请求写入权限。
  • 用户隐私: 清楚地告知用户将访问哪些目录,并解释访问的原因。 确保你的应用遵循相关的数据隐私法规。
  • 错误处理: 对可能出现的错误进行妥善处理,避免向用户暴露敏感信息。
  • 及时更新: 保持浏览器和相关库的更新,以获得最新的安全补丁。

通过以上方案,应该可以有效地解决 window.showDirectoryPicker() not allowed: File picker already active 错误。 选择合适的方案取决于你的应用场景和具体需求。 增加防抖机制适用于处理用户快速点击的场景,而使用状态标记或检查并重置异常状态则更适用于处理异步操作或复杂逻辑中的问题。 实施时应结合实际情况,选择最有效和最安全的解决方案。