在继续之前,页面不会等待另一页完成任务

A. *_*Lau 10 node.js headless-browser puppeteer

所以这是代码片段:

    for (let item of items)
    {
        await page.waitFor(10000)
        await page.click("#item_"+item)
        await page.click("#i"+item)

        let pages = await browser.pages()
        let tempPage = pages[pages.length-1]

        await tempPage.waitFor("a.orange", {timeout: 60000, visible: true})
        await tempPage.click("a.orange")

        counter++
    }
Run Code Online (Sandbox Code Playgroud)

page并且tempPage是两个不同的页面.

会发生什么是page等待10秒,然后点击一些东西,这会打开第二页.

应该发生的是tempPage等待一个元素,点击它,然后页面应该等待10秒再重复一遍.

然而,实际发生的是page等待10秒,点击这些东西,然后开始等待10秒而不等待tempPage完成其任务.

这是一个错误,还是我误解了什么?我应该如何解决这个问题,以便当for循环再次循环时,它只在tempPage点击之后.

Lou*_*uis 5

通常,在"完成其任务" await tempPage.click("a.orange")之前,您不能依赖暂停执行tempPage.对于同步执行的超级简单代码,它可能有效.但总的来说,你不能依赖它.

如果click触发了Ajax操作,或者启动了CSS动画,或者启动了无法立即计算的计算,或者打开了新页面等,那么您正在等待的结果是异步的,并且该.click方法不会等待这个异步操作要完成.

你能做什么?在某些情况下,您可以挂钩页面上运行的代码并等待一些对您很重要的事件.例如,如果您想等待Ajax操作完成并且页面上的代码使用jQuery,那么您可能会ajaxComplete用来检测操作何时完成.如果您无法挂钩到任何事件系统以检测操作何时完成,那么您可能需要轮询页面以等待操作完成的证据.

这是一个显示问题的示例:

const puppeteer = require('puppeteer');

function getResults(page) {
    return page.evaluate(() => ({
        clicked: window.clicked,
        asynchronousResponse: window.asynchronousResponse,
    }));
}

puppeteer.launch().then(async browser => {
    const page = await browser.newPage();
    await page.goto("https://example.com");
    // We add a button to the page that will click later.
    await page.evaluate(() => {
        const button = document.createElement("button");
        button.id = "myButton";
        button.textContent = "My Button";
        document.body.appendChild(button);
        window.clicked = 0;
        window.asynchronousResponse = 0;
        button.addEventListener("click", () => {
            // Synchronous operation
            window.clicked++;

            // Asynchronous operation.
            setTimeout(() => {
                window.asynchronousResponse++;
            }, 1000);
        });
    });

    console.log("before clicks", await getResults(page));

    const button = await page.$("#myButton");
    await button.click();
    await button.click();
    console.log("after clicks", await getResults(page));

    await page.waitForFunction(() => window.asynchronousResponse === 2);
    console.log("after wait", await getResults(page));

    await browser.close();
});
Run Code Online (Sandbox Code Playgroud)

setTimeout代码模拟通过点击开始任何一种异步操作.

运行此代码时,您将在控制台上看到:

before click { clicked: 0, asynchronousResponse: 0 }
after click { clicked: 2, asynchronousResponse: 0 }
after wait { clicked: 2, asynchronousResponse: 2 }
Run Code Online (Sandbox Code Playgroud)

您会看到clicked两次单击立即增加两次.但是,它需要一段时间才能asynchronousResponse增加.该语句await page.waitForFunction(() => window.asynchronousResponse === 2)轮询页面,直到我们等待的条件得到实现.


您在评论中提到按钮正在关闭选项卡.打开和关闭选项卡是异步操作.这是一个例子:

puppeteer.launch().then(async browser => {
    let pages = await browser.pages();
    console.log("number of pages", pages.length);
    const page = pages[0];
    await page.goto("https://example.com");
    await page.evaluate(() => {
        window.open("https://example.com");
    });

    do {
        pages = await browser.pages();
        // For whatever reason, I need to have this here otherwise
        // browser.pages() always returns the same value. And the loop
        // never terminates.
        await page.evaluate(() => {});
        console.log("number of pages after evaluating open", pages.length);
    } while (pages.length === 1);

    let tempPage = pages[pages.length - 1];

    // Add a button that will close the page when we click it.
    tempPage.evaluate(() => {
        const button = document.createElement("button");
        button.id = "myButton";
        button.textContent = "My Button";
        document.body.appendChild(button);
        window.clicked = 0;
        window.asynchronousResponse = 0;
        button.addEventListener("click", () => {
            window.close();
        });
    });

    const button = await tempPage.$("#myButton");
    await button.click();

    do {
        pages = await browser.pages();
        // For whatever reason, I need to have this here otherwise
        // browser.pages() always returns the same value. And the loop
        // never terminates.
        await page.evaluate(() => {});
        console.log("number of pages after click", pages.length);
    } while (pages.length > 1);

    await browser.close();
});
Run Code Online (Sandbox Code Playgroud)

当我运行上述内容时,我得到:

number of pages 1
number of pages after evaluating open 1
number of pages after evaluating open 1
number of pages after evaluating open 2
number of pages after click 2
number of pages after click 1
Run Code Online (Sandbox Code Playgroud)

您可以看到它需要一点时间window.open()window.close()具有可检测的效果.


在你的评论中你还写道:

我认为await基本上是将异步函数转换为同步函数

我不会说它将异步函数转换为同步函数.它使当前代码等待异步操作的承诺被解决或拒绝.但是,更重要的是,对于这里的问题,问题是你有两个虚拟机执行JavaScript代码:运行的Node puppeteer和控制浏览器的脚本,以及浏览器本身有自己的JavaScript虚拟机.您在节点端使用的任何内容await仅影响节点代码:它与浏览器中运行的代码无关.

当你看到类似的东西时会让人感到困惑await page.evaluate(() => { some code; }).它看起来像是一个整体,并且都在同一个虚拟机中执行,但事实并非如此.puppeteer获取传递给的参数.evaluate,将其序列化,并将其发送到执行它的浏览器.尝试添加类似await page.evaluate(() => { button.click(); });上面脚本中的内容const button = ....像这样的东西:

const button = await tempPage.$("#myButton");
await button.click();
await page.evaluate(() => { button.click(); });
Run Code Online (Sandbox Code Playgroud)

在脚本中,button之前定义的page.evaluate,但你会得到一个ReferenceErrorpage.evaluate运行,因为button在浏览器端是没有定义!