浏览器扩展开发:waitForAnyElement 的多次调用真的有必要吗?
最近在做微博自动填充功能的时候,有个小伙伴问我:为啥你的 handleWeiboFill 里要反复调用 waitForAnyElement,看起来好像有点重复劳动啊?
这问题问得挺好!表面上看确实像是在重复调用同一个函数,但仔细分析就会发现,每次调用其实都在干不同的事儿,各有各的用处。
四次调用都在干嘛
第一次:快速检查编辑器是否存在
let editorResult = await waitForAnyElement(selectors, {
visibleOnly: true,
timeout: 15000
})这是在干嘛:先看看编辑器是不是已经在那儿了(比如用户手快已经点开了发微博框)
为啥要这么干:这是最快的路子,要是能找到就直接开始填充,省得再点按钮了
第二次:点击按钮后等编辑器加载
editorResult = await waitForAnyElement(selectors, {
visibleOnly: true,
timeout: 5000
})这是在干嘛:点完”发微博”按钮后,等着编辑器真正加载到页面上
为啥要这么干:光点按钮不行,得确认编辑器确实出来了才能操作
第三次:兜底方案找编辑器
editorResult = await waitForAnyElement(universalSelectors, {
visibleOnly: true,
timeout: 5000
})这是在干嘛:要是前面两种方法都找不到,就用通用的选择器再试试
为啥要这么干:微博说不定哪天就改版了,class名一变原来的选择器就废了,得有个备选方案
第四次:确认编辑器能正常用
const visible = await waitForAnyElement(selectors, {
visibleOnly: true,
timeout: 2000
})
if (!visible || !isVisibleStrict(editor)) {
throw new Error("微博编辑器不可交互")
}这是在干嘛:就算编辑器在页面上,也可能被遮住或者没激活,得再确认一下
为啥要这么干:很多网站都是先把DOM挂上去,要用户点一下才能真正用,不检查这个容易填充失败
为啥不能只调用一次
看起来好像是在重复调用同一个函数,但其实每次都在检查不同的东西:
- 第一次:看看编辑器是不是已经打开了
- 第二次:点了按钮后等着编辑器加载
- 第三次:前面都找不到的话用备用方案
- 第四次:确认编辑器真的能用
waitForAnyElement 这个函数挺智能的,既能马上返回已经存在的元素,也能等着异步加载的元素。每次调用虽然函数一样,但等的目标完全不一样。
要是只调用一次,可能会漏掉很多情况:
- 用户点按钮后新加载的编辑器抓不到
- 编辑器虽然找到了但是被遮住了,填充了也没用
- 网站改版后原来的选择器失效了
怎么让代码更好看
现在这样写逻辑挺清楚的,但看起来确实有点重复。可以试试这两个优化思路:
方案一:封装成状态机
async function getEditorForWeibo() {
// 快速路径 → 点击按钮 → 兜底方案 → 确认可用
// 内部自动处理所有等待逻辑
return await findEditorWithRetry()
}外面调用的时候只需要一次:
const editor = await getEditorForWeibo()方案二:增强等待函数
// 增加模式参数,一个函数搞定所有情况
await waitForAnyElement(selectors, {
modes: ["exist", "interactive", "fallback"],
timeout: 15000
})最后说几句
多次调用 waitForAnyElement 不是为了凑数,而是每个阶段都需要确认不同的状态。写浏览器扩展就是这样,得考虑到各种可能的情况,代码才能稳定可靠。
不过为了代码看起来更舒服,确实可以把它封装得更好一些。我自己就更喜欢状态机的方案,把复杂的等待逻辑藏在里面,外面用起来简单明了。
大家在写类似功能的时候,不妨也想想自己的等待逻辑是不是覆盖了所有可能的情况。有时候多等一次,就能少很多莫名其妙的bug。