Thường là, bạn không thể dựa vào await tempPage.click("a.orange")
để tạm dừng thực hiện cho đến khi tempPage
có "hoàn thành [ed] nhiệm vụ của mình". Đối với mã siêu đơn giản thực hiện đồng bộ, nó có thể hoạt động. Nhưng nói chung, bạn không thể dựa vào nó.
Nếu nhấp chuột kích hoạt hoạt động Ajax hoặc bắt đầu hoạt ảnh CSS hoặc bắt đầu tính toán không thể tính ngay lập tức hoặc mở trang mới, v.v ... thì kết quả bạn đang chờ là không đồng bộ và .click
phương thức này sẽ không đợi thao tác không đồng bộ này hoàn tất.
Bạn có thể làm gì? Trong một số trường hợp, bạn có thể móc vào mã đang chạy trên trang và đợi một số sự kiện quan trọng đối với bạn. Ví dụ, nếu bạn muốn đợi cho một thao tác Ajax được thực hiện và mã trên trang sử dụng jQuery, thì bạn có thể sử dụng ajaxComplete
để phát hiện khi hoạt động hoàn tất. Nếu bạn không thể móc nối vào bất kỳ hệ thống sự kiện nào để phát hiện khi hoạt động được thực hiện, thì bạn có thể cần phải thăm dò trang để chờ bằng chứng cho thấy hoạt động được thực hiện.
Dưới đây là một ví dụ cho thấy các vấn đề:
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();
});
Mã setTimeout
mô phỏng bất kỳ loại hoạt động không đồng bộ bắt đầu bằng cách nhấp chuột.
Khi bạn chạy mã này, bạn sẽ thấy trên giao diện điều khiển:
before click { clicked: 0, asynchronousResponse: 0 }
after click { clicked: 2, asynchronousResponse: 0 }
after wait { clicked: 2, asynchronousResponse: 2 }
Bạn thấy rằng clicked
được ngay lập tức tăng lên gấp đôi bởi hai cú nhấp chuột. Tuy nhiên, phải mất một lúc trước khi asynchronousResponse
được tăng lên. Tuyên bố await page.waitForFunction(() => window.asynchronousResponse === 2)
thăm dò ý kiến trang cho đến khi điều kiện chúng tôi chờ đợi được thực hiện.
Bạn đã đề cập trong nhận xét rằng nút đang đóng tab. Các tab mở và đóng là các hoạt động không đồng bộ.Dưới đây là một ví dụ:
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();
});
Khi tôi chạy ở trên, tôi nhận được:
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
Bạn có thể nhìn thấy nó mất một chút trước khi window.open()
và window.close()
có tác dụng phát hiện.
Trong bình luận của bạn, bạn cũng viết:
Tôi nghĩ await
là cơ bản những gì đã biến một chức năng không đồng bộ thành đồng bộ một
Tôi sẽ không nói nó quay chức năng không đồng bộ vào đồng bộ những người. Nó làm cho mã hiện tại chờ đợi lời hứa của một hoạt động không đồng bộ được giải quyết hoặc bị từ chối. Tuy nhiên, quan trọng hơn cho vấn đề ở đây, vấn đề là bạn có hai máy ảo thực thi mã JavaScript: có Node chạy puppeteer
và tập lệnh điều khiển trình duyệt và chính trình duyệt có máy ảo JavaScript của riêng nó. Bất kỳ await
mà bạn sử dụng ở phía nút Node chỉ ảnh hưởng đến mã Node: nó không có ổ đĩa trên mã chạy trong trình duyệt.
Nó có thể gây nhầm lẫn khi bạn nhìn thấy những thứ như await page.evaluate(() => { some code; })
. Dường như nó là tất cả của một phần, và tất cả thực hiện trong cùng một máy ảo, nhưng nó không phải là. puppeteer
lấy tham số được chuyển đến .evaluate
, tuần tự hóa nó và gửi nó tới trình duyệt, nơi nó thực thi. Thử thêm một cái gì đó như await page.evaluate(() => { button.click(); });
trong tập lệnh ở trên, sau const button = ...
. Một cái gì đó như thế này:
const button = await tempPage.$("#myButton");
await button.click();
await page.evaluate(() => { button.click(); });
Trong kịch bản, button
được định nghĩa trước page.evaluate
, nhưng bạn sẽ nhận được một ReferenceError
khi page.evaluate
chạy vì button
không được định nghĩa ở phía trình duyệt!