Puppeteer 等待页面和 setTimeout 在 Firebase 云功能中不起作用

Posted

技术标签:

【中文标题】Puppeteer 等待页面和 setTimeout 在 Firebase 云功能中不起作用【英文标题】:Puppeteer wait for page and setTimeout not working in Firebase cloud functions 【发布时间】:2020-03-02 06:05:25 【问题描述】:

我正在尝试加载我在 firebase 上托管的页面,并使用 Puppeteer 将其转换为 pdf。 它只适用于一个 html 页面。 现在我从 firebase 获取数据并将其显示在我的页面中,因此我需要等待页面完全加载,然后才能创建 pdf。 当我使用 firebase 模拟器 npm run serve 在本地测试它时,效果很好。 但是它在云函数中不起作用,settimout 只是一直等到函数超时。 日志显示waitFor,一段时间后显示Function execution took 120002 ms, finished with status: 'timeout'。 我尝试了很多我不知道该怎么做的事情,我开始认为这是云功能中的错误。

import * as functions from 'firebase-functions';
// tslint:disable-next-line:no-duplicate-imports
import  VALID_MEMORY_OPTIONS  from 'firebase-functions';
// import * as puppeteer from 'puppeteer';

const runtimeOpts = 
  timeoutSeconds: 120,
  memory: VALID_MEMORY_OPTIONS[4],
;

// const cors = require('cors')( origin: true );

export const generatePDF = functions
  .runWith(runtimeOpts)
  // .region('europe-west1')
  .https.onRequest(async (request: any, response: any) => 
    // cors(request, response, async () => 
    console.log(7);

    const hostname = request.hostname;

    let url = '';
    if (hostname === 'localhost') 
      url = 'http://localhost:5000';
     else 
      url = 'https://myapp.firebaseapp.com';
    

    const puppeteer = require('puppeteer');

    console.log('launch puppeteer');

    const browser = await puppeteer.launch(
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    );
    // debug: headless: false

    console.log('new page');

    const page = await browser.newPage();

    console.log('goto');

    await page
      .goto(`$url/report/A3p71Fl5GD98Sjks5BJg`)
      .catch((error: any) => 
        console.log(error);
        return response.send('Timeout1');
      );

    console.log('waitFor');

    // await page.waitFor(10000).catch((error: any) => 
    //   console.log(error);
    //   return response.send('Timeout2');
    // );

    await new Promise(resolve => setTimeout(resolve, 5000));

    // await page.waitForNavigation(
    //   waitUntil: 'networkidle0',
    // );

    // Wait for element to render
    // await page.waitForSelector('#end');

    // await page.waitFor(10000);

    console.log('create pdf');

    const pdf = await page.pdf(
      format: 'A4',
    );

    console.log('close browser');

    await browser.close();

    // response.setHeader('Content-Disposition', 'attachment; filename=customfilename.pdf');
    response.setHeader('Content-Disposition', 'filename=customfilename.pdf');
    return response.type('application/pdf').send(pdf);
    // );
  );

export const helloWorld = functions.https.onRequest(
  async (request: any, response: any) => 
    await new Promise(resolve => setTimeout(resolve, 5000));
    response.send('Hello from Firebase!');
  ,
);

// function delay(ms: number) 
//   return new Promise(resolve => setTimeout(resolve, ms));
// 

package.json:


  "name": "functions",
  "scripts": 
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  ,
  "engines": 
    "node": "8"
  ,
  "main": "lib/index.js",
  "dependencies": 
    "cors": "^2.8.5",
    "firebase-admin": "^8.7.0",
    "firebase-functions": "^3.3.0",
    "puppeteer": "^2.0.0"
  ,
  "devDependencies": 
    "tslint": "^5.12.0",
    "typescript": "^3.6.3"
  ,
  "private": true

tsconfig.json:


  "compilerOptions": 
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017",
    "lib": ["dom"]
  ,
  "compileOnSave": true,
  "include": ["src"]

【问题讨论】:

与您的问题无关,但您为什么要这样定义回调函数:(request: any, response: any)?您不必将这些参数向下转换为类型any。 TypeScript 应该根据 firebase-functions 提供的类型绑定自动获取它们的值。我最近经常看到人们这样做,我不知道为什么。 因为 tslint 抱怨它,很烦人 我无条件使用 tslint,我从未见过它抱怨。您是否在 Firebase CLI 为您做的事情之外对配置做一些事情?我超级好奇。这根本不应该是一个问题。 你是对的,我删除了它并且 tslint 没有抱怨,我使用 vscode 和更漂亮。我只看到那条黄线一次并补充说:任何,但现在看不到它,很奇怪。 好的。顺便说一句,我从来没有遇到过使用 setTimeout 在 Cloud Functions 中实现等待的问题。 【参考方案1】:

您应该在您的函数之外导入 puppeteer,以便 firebase 可以重用它。此外,您不需要创建超时,但您可以使用 await page.waitFor(5000); 代替。我试图为您的用例创建一个最小的示例。如果您仍然遇到错误,请转到您的 firebase 函数控制台并检查日志,它们应该会告诉您出了什么问题。

还有一种可能是您没有在您的 firebase 帐户上启用结算功能 - 在这种情况下,您的函数无法访问 3rd 方主机。

import * as functions from 'firebase-functions';
import * as puppeteer from 'puppeteer';

export const generatePDF = functions
    .runWith( timeoutSeconds: 30, memory: "1GB" )
    .https.onRequest(async (request, response) => 

        const url = 'https://www.yoururl.com/';
        const browser = await puppeteer.launch( headless: true, args: ['--no-sandbox'] );

        const page = await browser.newPage();
        await page.goto(url);
        await page.waitFor(5000);

        const pdf = await page.pdf(
            format: 'A4',
        );

        await browser.close();

        response.setHeader('Content-Disposition', 'filename=customfilename.pdf');
        return response.type('application/pdf').send(pdf);
    );

【讨论】:

谢谢,我必须为此安装 puppeteer 类型?上周 2.0.0 版本不可用,但我会尝试旧版本 您不必这样做,但是非常方便!成功了吗? 如果我去firebase函数控制台日志,什么都没有,只是超时 在这种情况下,我高度怀疑您超时的原因是您选择的 URL。我复制粘贴了上面的确切代码(在此处调整此页面的 URL)并创建了一个云功能。您可以在此处查看结果(加载需要几秒钟,但随后您将获得 PDF):us-central1-ankerguru.cloudfunctions.net/generatePDF 但是如果我在本地模拟器中使用相同的 url 它确实可以工作,它也可以在这里工作:puppeteersandbox.com 没有问题,有时甚至我的云功能也可以工作

以上是关于Puppeteer 等待页面和 setTimeout 在 Firebase 云功能中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在 puppeteer 中如何等待弹出页面完成加载?

Puppeteer + Nodejs 通用全屏网页截图方案常用参数实现

使用 Puppeteer 等待组件上的 ID 更改

puppeteer快速调试

Puppeteer 等待所有图像加载然后截图

puppeteer:在继续下一行之前等待 N 秒