首页
/ Puppeteer EvaluateFunc 类型深度解析:掌握 evaluate 系列方法的类型契约与底层机制

Puppeteer EvaluateFunc 类型深度解析:掌握 evaluate 系列方法的类型契约与底层机制

2026-09-06 17:49:46作者:庞队千Virginia

EvaluateFunc 是 Puppeteer 中 page.evaluateframe.evaluateHandlerealm.waitForFunction 等执行页面脚本的核心类型签名,它约束了"在浏览器上下文里执行的页面函数"的参数形态与返回值形态。本文基于 EvaluateFunc 官方类型文档puppeteer-core 源码 展开,带你完整理解该类型的定义、它在 API 泛型体系中的位置、页面函数的传参与序列化规则,以及如何在实际脚本中写出类型安全、可运行的 evaluate 代码。

一、EvaluateFunc 的完整类型签名

根据 类型文档EvaluateFunc 的签名如下:

export type EvaluateFunc<T extends unknown[]> = (
  ...params: InnerParams<T>
) => Awaitable<unknown>;

它由三个要素构成:

  1. 泛型参数 T extends unknown[]T 代表调用方传入的参数元组(tuple)。例如 page.evaluate(fn, 'a', 42) 中,T 会推导为 ['a', 42]
  2. 参数 ...params: InnerParams<T>:通过 InnerParams 映射类型T 逐项做"句柄展平"处理,使得页面函数的参数既可以接收原始值,也可以接收 JSHandle/ElementHandle
  3. 返回值 Awaitable<unknown>:通过 Awaitable 类型 允许页面函数同步返回,也可以返回 Promise——Puppeteer 会等待该 Promise 在页面中解决后取回其值。

源码定义位置

该类型的实际定义位于 common/types.ts

/**
 * @public
 */
export type EvaluateFunc<T extends unknown[]> = (
  ...params: InnerParams<T>
) => Awaitable<unknown>;

二、逐层拆解:InnerParams、FlattenHandle 与 Awaitable

要理解 EvaluateFunc,必须理解它引用的两个工具类型。它们同样定义在 common/types.ts 中。

Awaitable:同步值与 Promise 的统一

// packages/puppeteer-core/src/common/types.ts
export type Awaitable<T> = T | PromiseLike<T>;

Awaitable<T> 表示"要么是 T,要么是能解决为 T 的类 Promise"。这解释了为什么下面两种写法都是合法的 pageFunction

// 同步返回
const sum = await page.evaluate((a, b) => a + b, 3, 4);

// 返回 Promise,Puppeteer 会等待其解决
const result = await page.evaluate(() => {
  return Promise.resolve(8 * 7);
});
console.log(result); // 56

上例中 Promise 示例直接来自 Realm 类源码注释

InnerParams:参数元组的逐项展平

// packages/puppeteer-core/src/common/types.ts
export type InnerParams<T extends unknown[]> = {
  [K in keyof T]: FlattenHandle<T[K]>;
};

InnerParams 是一个映射类型,它遍历参数元组 T 的每个位置,将每个参数类型替换为其 FlattenHandle 形态。而 FlattenHandle 依赖 HandleOr / HandleFor

/**
 * @public
 */
export type HandleFor<T> = T extends Node ? ElementHandle<T> : JSHandle<T>;

/**
 * @public
 */
export type HandleOr<T> = HandleFor<T> | JSHandle<T> | T;

/**
 * @public
 */
export type FlattenHandle<T> = T extends HandleOr<infer U> ? U : never;

从源码结构看,这套类型组合的效果是:页面函数第 N 个参数的静态类型被允许写成"值本身"或"值的句柄"(JSHandle<T> / ElementHandle<T>),而实际传入时两者皆可。这也正是文档中"可以传入 JSHandle 实例作为函数参数"的类型层支撑——运行时传入句柄时,Puppeteer 会通过句柄引用传递而非序列化值。

三、EvaluateFunc 在 API 泛型体系中的位置

EvaluateFunc 不是孤立的类型,而是整个 evaluate 家族的"函数形参约束"。它作为泛型约束出现在 RealmFramePageElementHandleJSHandleWebWorker 的 API 签名中。

Realm:抽象基座

Realm 类 定义了三个关键方法的抽象签名,全部以 Func extends EvaluateFunc<Params> = EvaluateFunc<Params> 作为约束:

abstract evaluateHandle<
  Params extends unknown[],
  Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
  pageFunction: Func | string,
  ...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;

abstract evaluate<
  Params extends unknown[],
  Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
  pageFunction: Func | string,
  ...args: Params
): Promise<Awaited<ReturnType<Func>>>;

注意两个返回类型的构造方式:

  • evaluate 返回 Promise<Awaited<ReturnType<Func>>>——外层 Promise 是 Node 侧的等待,Awaited<> 会把页面函数返回的 Promise 解包为最终值类型。若页面函数声明 () => Promise<number>evaluate 的静态返回就是 Promise<number>
  • evaluateHandle 返回 Promise<HandleFor<Awaited<ReturnType<Func>>>>——若返回值是 Node,句柄类型为对应的 ElementHandle,否则为 JSHandle,这正对应 HandleFor 的条件类型。

waitForFunction 同样受此约束(见 Realm.ts L148-L160),它轮询执行 pageFunction,直到返回真值,最终解析为 Promise<HandleFor<Awaited<ReturnType<Func>>>>

Frame 与 Page:薄封装层

Frame.evaluateFrame.evaluateHandlePage.evaluate / Page.evaluateHandle 在 frame 上下文中的等价实现,其签名与 Realm 完全同构,实现上仅做两件事:

@throwIfDetached
async evaluate<
  Params extends unknown[],
  Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
  pageFunction: Func | string,
  ...args: Params
): Promise<Awaited<ReturnType<Func>>> {
  pageFunction = withSourcePuppeteerURLIfNone(
    this.evaluate.name,
    pageFunction,
  );
  return await this.mainRealm().evaluate(pageFunction, ...args);
}

即:打上来源 URL 标记(便于在浏览器端堆栈中定位调用方),然后委托给 mainRealm()Page.evaluate 最终落到主 frame 的 mainRealm(),因此理解 EvaluateFunc 就等于理解了整条 Page → Frame → Realm → 执行上下文 的调用链的函数类型契约。

四、实战:在约束下编写页面函数

基本用法与类型推导

const result = await page.evaluate(
  (a, b) => a + b,   // Func 被推导,T = [number, number]
  35,
  12,
);
// result: 47

TypeScript 下 Func 由你传入的函数字面量推导,Params 由后续 ...args 推导,EvaluateFunc<Params> 约束保证函数参数个数与传参个数匹配。

返回 Promise(Awaitable 的价值)

const value = await page.evaluate(() => {
  return new Promise(resolve => {
    setTimeout(() => resolve('done'), 100);
  });
});
// value: 'done'

因为 EvaluateFunc 的返回值是 Awaitable<unknown>,页面内的异步逻辑可以被透明等待;Promise<Awaited<ReturnType<Func>>> 的返回类型则让 Node 侧拿到的是"已解包"的最终值。

字符串表达式形式

签名中 pageFunction: Func | string 表明第一参数也接受字符串表达式,此时不会做 EvaluateFunc 推导,而是按 JS 表达式求值。Frame.ts 的注释给出了典型示例:

// 字符串形式:作为表达式在页面执行
await page.waitForFunction('window.innerWidth < 100');

从源码结构看,字符串与函数在运行时走的是不同的编译路径:函数会被序列化后在浏览器端重建,字符串则被包装为表达式直接执行。因此能用函数就用函数——函数形式保留完整参数传递与类型推导能力。

传参与序列化的边界

  • 普通参数按值传递args 会被序列化后克隆进页面上下文。函数、undefinedNaNInfinity 等不可序列化值不能作为参数,也不能作为返回值。
  • JSHandle/ElementHandle 按引用传递:如 EvaluateFunc 相关 API 文档 所述,句柄参数在页面内以原始对象形态出现,这也是 InnerParams 允许 FlattenHandle 形态的原因。
const titleHandle = await page.title().then(() => null) || null;
// 更典型的:
const handle = await page.evaluateHandle(() => document.title);
const upper = await page.evaluate(
  h => h.toUpperCase(),
  handle,        // ElementHandle/JSHandle 作为参数
  handle,
);

姊妹类型:EvaluateFuncWith

common/types.ts 中还定义了 EvaluateFunc 的变体,用于需要"固定首参"的场景(如 ElementHandle.evaluate 中页面函数第一个参数恒为该元素本身):

export type EvaluateFuncWith<V, T extends unknown[]> = (
  ...params: [V, ...InnerParams<T>]
) => Awaitable<unknown>;

V 是被注入的首参类型,后续参数同样享受 InnerParams 展平。这一类型与 EvaluateFunc 互为补充,共同覆盖"无注入参数"与"有注入参数"两类页面函数。

五、适用前提与限制

  • EvaluateFunc 是纯类型层契约(@public 类型导出),它本身不产生运行时行为;真正的执行由 CDP(packages/puppeteer-core/src/cdp/)与 BiDi(packages/puppeteer-core/src/bidi/)两套协议实现分别落地。
  • 页面函数运行在浏览器上下文,无法直接访问 Node.js 全局对象;需要 Node 侧能力时应使用 page.exposeFunction 反向桥接(见 Page.exposeFunction 源码)。
  • frame/realm 在 detach 后调用 evaluate 会抛出错误(@throwIfDetached 装饰器见 Frame.ts L480-L492),编写长任务时应留意页面导航时机。

六、小结

EvaluateFunc<T extends unknown[]> = (...params: InnerParams<T>) => Awaitable<unknown> 一行类型,承载了 Puppeteer 脚本执行的三大契约:参数可值可句柄(InnerParams/FlattenHandle)、返回值可同步可异步(Awaitable)、泛型链贯穿 Page/Frame/Realm/ElementHandle 全链路。掌握它之后,你在写 page.evaluate 时遇到的类型推导、句柄传参、Promise 等待行为都有了明确的源码依据。相关类型文档可继续参考:InnerParamsAwaitableEvaluateFuncWithFlattenHandle

登录后查看全文
热门项目推荐
相关项目推荐