NodeJS 在调用处理程序之前,AWS Lambda不会在处理程序外部执行Promise

bzzcjhmw  于 2023-02-21  发布在  Node.js
关注(0)|答案(1)|浏览(141)

我正在开发一个aws lambda,它需要为bucket中的每个新s3对象启动一个puppeteer浏览器,浏览器启动代码在初始调用时花费了很长时间,所以我想我应该把启动代码放在处理程序之外,并使用Provisioned Concurrency来使浏览器在新文件插入bucket时准备就绪。
它似乎调用了promise,因为在进行任何实际调用之前,我会收到日志,上面写着“从配置的并发示例中获取可执行路径。但是,在进行实际的lambda调用之前,它从来没有输出消息“启动浏览器”。如果promise chromium.executablePath在处理程序之外,为什么在进行调用之前它不会完成?

let startTime = Date.now();

const chromium = require("chrome-aws-lambda");
const AWS = require("aws-sdk");
const s3 = new AWS.S3();
const { createSSRApp } = require("vue");
const { renderToString } = require("vue/server-renderer");
const path = require("path");
const fs = require("fs");
const manifest = require("../../compiled/ssr-manifest.json");

console.log("Load packages: " + (Date.now() - startTime));

const browserPromise = new Promise((res) => {
  const browserStartTime = Date.now();
  console.log("Getting executable path");
  chromium.executablePath.then((executablePath) => {
    console.log("Launching browser");
    chromium.puppeteer
      .launch({
        args: chromium.args,
        defaultViewport: chromium.defaultViewport,
        executablePath: executablePath,
        headless: true,
      })
      .then((browser) => {
        res(browser);
        console.log("Start headless browser: " + (Date.now() - browserStartTime));
      });
  });
});
browserPromise.then(() => console.log("Started Headless Browser"));

/**
 * A Lambda function that logs the payload received from S3.
 */
exports.handler = async (event, context) => {
  const bucketName = event.Records[0].s3.bucket.name;
  const objectKey = event.Records[0].s3.object.key;
  const browser = await browserPromise;
  ... //use browser code
}

如果我需要这个文件在本地的另一个节点文件中,它可以很好地运行promise,而不需要调用handler函数,所以这一定是一些我不理解的lambda环境特定的东西。有人对此有什么见解吗?

nfzehxib

nfzehxib1#

您所描述的问题是由于对异步代码的控制不佳造成的。

延迟示例化浏览器承诺

当您使用new关键字示例化Promise时,您提供的函数将立即开始执行。

const x = new Promise(res => console.log('test'))

这将立即打印'test',而不需要.thenawait,这就是为什么您的代码立即打印'Getting executable path',而不是等待lambda的请求事件。
要解决这个问题,在请求真正发生之前不要示例化这个承诺,将你的承诺构造移到一个函数中,当请求发生时,你可以从处理程序中调用它。

async function startBrowser () {
  // code to start browser
  return browser
}

exports.handler = async (event, context) => {
  const bucketName = event.Records[0].s3.bucket.name;
  const objectKey = event.Records[0].s3.object.key;
  const browser = await startBrowser();
  // use browser
}

修复异步返回流

第二,你需要让你的startBrowser函数实际返回一个浏览器。因为你还没有await编辑任何在你的browserPromise中创建的承诺,它会触发代码启动chromium,但会立即解决。浏览器启动需要一段时间,这就是为什么你直到很久以后才看到'Launching browser'
若要解决此问题,请确保在浏览器准备就绪之前不会解析浏览器承诺,然后返回浏览器对象以便使用。

function startBrowser () {
  const browserStartTime = Date.now();
  console.log("Getting executable path");

  // await this promise
  const browser = await chromium.executablePath.then((executablePath) => {
    console.log("Launching browser");

    // return browser promise
    return chromium.puppeteer
      .launch({
        args: chromium.args,
        defaultViewport: chromium.defaultViewport,
        executablePath: executablePath,
        headless: true,
      })

      console.log("Start headless browser: " + (Date.now() - browserStartTime));

  return browser
}

exports.handler = async (event, context) => {
  const bucketName = event.Records[0].s3.bucket.name;
  const objectKey = event.Records[0].s3.object.key;
  const browser = await startBrowser();
  // use browser
}

通过跨请求共享浏览器来提高性能

您可以通过将浏览器保存为单例来进一步提高性能,这样就不需要在每个请求周期都示例化一个新浏览器。

let browser; // singleton/global browser object

// start a browser to be used for all requests
// this will take a little time, so hold a reference to the promise so we
// can know when it is ready to use
const browserPromise = startBrowser.then(newBrowser => { browser = newBrowser }

exports.handler = async (event, context) => {
  const bucketName = event.Records[0].s3.bucket.name;
  const objectKey = event.Records[0].s3.object.key;

  // if the first request comes before the browser is ready, we should
  // wait for the promise to resolve
  if (!browser) await browserPromise

  // use browser
}

有关内存使用情况的说明

浏览器可能会占用大量内存,也可能会泄漏内存。如果修复你的异步代码仍然不能使lambda工作,请考虑加载一个网页可能会消耗千兆字节的内存,特别是对于大型和复杂的页面(例如谷歌Map)。我对lambda没有经验,但你可能会发现自己遇到内存限制。

相关问题