为什么这个函数不起作用(等待 Javascript 中的元素)?

Why doesn't this function work (waiting for an element in Javascript)?

基本上,我们有一个 setTimeout() 函数,它在创建元素并将其附加到正文之前等待半秒。然后我们有一个名为 foo() 的异步函数,它尝试定位该元素 (while(!expression)) 并在找不到它时等待承诺。承诺只等待十分之一秒并解决。

我设想的这种工作方式是 while 循环运行的前五次,元素未找到并且 promise 已解决。然后在第六次,顶部的 setTimeout() 函数创建元素,while 循环表达式解析为 true。这意味着到达 foo().then() 表达式中的 console.log() (并且代码可以继续。

我认为这是时间问题。当创建元素的代码从 setTimeout() 函数中取出时,代码执行得很好,所以看起来 while 循环在 top setTimeout() 函数可以触发之前无限期地 运行 。为什么是这样?有没有办法在不重写所有代码的情况下解决这个问题?似乎这个 foo() 函数,我在一个更大的代码库中,最近停止工作了。浏览器解析的方式是否发生了变化 Javascript?

注意:foo() 背后的想法是我在 then() 函数中编写的内容应始终等待创建由 expression 标识的元素。

setTimeout(function () {
    const el = document.createElement('div')
    el.id = 'bar'
    el.innerText = 'Bar'
    document.body.appendChild(el)
}, 500)

const promise = new Promise(function(resolve) {
    setTimeout(function() {
       resolve()
    }, 100)
})

async function foo(expression) {
    while(!expression) {
        await promise
    }
}

foo(document.getElementById('bar')).then(function() {
    console.log(document.getElementById('bar'))
})

有些不对的地方

首先是:

async function foo(expression) {
    while(!expression) {
        await promise
    }
}

永远不会做任何有用的事情。如果您为 expression 传递真值,则循环永远不会 运行。如果你为 expression 传递了一个错误的值,那么循环将是一个无限循环,循环永远不会停止,因为循环中没有代码会改变 expression 的值。

另请注意,expression 是作为一个值传递的,而不是一个可以再次调用的函数,因此它在传递时只有一个值,并且该值在该函数内永远不会改变。因此,使用 while() 条件对其进行测试实际上也没有任何用处。

此外,await promise 将始终等待相同的承诺,因此它会在第一次延迟,但该承诺将在后续时间得到解决,因此不会增加任何延迟。 await使用已解决的承诺不会再延迟。

然后,这个:

foo(document.getElementById('bar')).then(function() {
    console.log(document.getElementById('bar'))
})

总是将 document.getElementById('bar') 的结果传递给 foo(),这将是真实的或虚假的,这取决于文档中是否存在 #bar 元素。如果它是真实的,因为它存在,那么 foo() 将立即解决它的承诺,因为它永远不会进入 while 循环。如果 #bar 不存在,那么 foo() 将永远处于无限循环中。


使用 setInterval 实现

如果您试图“轮询”某个元素何时存在,请仅使用 setInterval() 或使用计数器来限制它 运行 的长度。它简单多了,不会受到任何这些问题的影响。

let cntr = 0;
const maxChecks = 100;
let timer = setInterval(() => {
    ++cntr;
    let item = document.getElementById('bar');
    if (item) {
        // found #bar
        clearInterval(timer);
        // do something with #bar
    } else {
        if (cntr > maxChecks) {
            clearInterval(timer);
            console.log("gave up checking for #bar");
        }
    }
}, 100);

实现使用带有 await 的 promise

如果你真的想使用 promise,你可以这样做:

function delay(t) {
    return new Promise(resolve => setTimeout(resolve, t));
}

async function waitForElement(id, maxWaitTime = 2000) {
    const start = Date.now();

    while (Date.now() - start < maxWaitTime) {
        let item = document.getElementById(id);
        if (item) {
            return item;
        } else {
            // wait a little and try again
            await delay(100);
        }
    }
    throw new Error("Element doesn't exist after max time");
}

这为您提供了一个函数,该函数等待(最长时间)元素存在,然后解析或拒绝返回的承诺。

 waitForElement("bar").then(elem => {
     console.log("element exists");
     console.log(elem.innerHTML);      // show contents of DOM element
 }).catch(err => {
     console.log("element does not exist after max time");
 });

可运行代码段

这里是 运行 可用代码段中的工作版本:

// create #bar element after a bit of time
setTimeout(function () {
    const el = document.createElement('div')
    el.id = 'bar'
    el.innerText = 'Bar exists now'
    document.body.appendChild(el)
}, 500);

function delay(t) {
    return new Promise(resolve => setTimeout(resolve, t));
}

async function waitForElement(id, maxWaitTime = 2000) {
    const start = Date.now();

    while (Date.now() - start < maxWaitTime) {
        let item = document.getElementById(id);
        if (item) {
            return item;
        } else {
            // wait a little and try again
            await delay(100);
        }
    }
    throw new Error("Element doesn't exist after max time");
}

// wait for #bar to exist and output it's contents

 waitForElement("bar").then(elem => {
     console.log("element exists now and its content is '" + elem.innerHTML + "'");
 }).catch(err => {
     console.log("element does not exist after max time");
 })

问题是 document.getElementById('bar') 的值被评估一次,结果存储在 expression 变量中。要使其在 while 循环中每次都求值,您需要传递一个将导致求值的回调,然后每次都调用它:

async function foo(callback) {
    while(!callback()) {
        await promise
    }
}

foo(() => document.getElementById('bar')).then(function() {
    console.log(document.getElementById('bar'))
})

请注意,我想帮助您解决这个具体问题并向您展示哪里出了问题,但我绝不是说在 DOM 上轮询查询是个好主意。

Puppeteer提供了三种轮询的方式来等待一个元素出现在DOM:

waitForPredicatePageFunction 将 运行 在 puppeteer 之外,没有其他 Puppeteer 依赖项。还有各种types.

的好处的简单说明

一个 promise 只能解决一次,所以如果你重复使用同一个 promise,只有第一次 500 毫秒的等待被兑现。