n8n节点开发:.call()调用时同时访问类实例this与thisArgs
问题:n8n节点开发中this上下文冲突问题
我正在开发一个n8n节点,本质是触发特定事件时运行的函数。我创建了一个抽象类供n8n环境调用,但无法调用其方法——因为n8n用Class.execute.call(thisArgs)调用函数,会覆盖类实例的this上下文。
n8n库调用我的类的方式
以下是从n8n源码中复制的调用逻辑:
import { createContext, Script } from 'vm' import { AbstractNode } from './n8n' const context = createContext({ require }) export const loadClassInIsolation = <T>(filePath: string, className: string) => { const script = new Script(`new (require('${filePath}').${className})()`) return script.runInContext(context) as T } async function run(): Promise<void> { const myClass = loadClassInIsolation<AbstractNode<unknown>>( '../dist/codex/node/Codex.node.js', 'Codex', ) const thisArgs = { prepareOutputData: (d: any): any => ({ ...d }), } console.log(await myClass.execute.call(thisArgs, thisArgs)) } void run()
我的抽象类
下面是使用this时出问题的抽象类:
import { IExecuteFunctions, INodeExecutionData, INodeType } from 'n8n-workflow' export abstract class AbstractNode<TParams> implements Omit<INodeType, 'description'> { private _executeFunctions: IExecuteFunctions = null set executeFunctions(value: IExecuteFunctions) { this._executeFunctions = value } get executeFunctions(): IExecuteFunctions { return this._executeFunctions } abstract run(t: TParams): Promise<INodeExecutionData> async execute(): Promise<INodeExecutionData[][]> { this.executeFunctions = this as unknown as IExecuteFunctions // 此行报错:TypeError: this.run is not a function await this.run({ prompts: ['hello', 'world'] } as TParams) return this.executeFunctions.prepareOutputData([ { json: { answer: 'Sample answer' } }, ]) } }
动态实例化的类
这个类实现了AbstractNode的抽象run方法:
import { Logger } from '@nestjs/common' import { FirefliesContext } from '@src/common' import { AbstractNode } from '@src/n8n' import { INodeExecutionData } from 'n8n-workflow' type CodexParams = { prompts: string[] } export class Codex extends AbstractNode<CodexParams> { run({ prompts }: CodexParams): Promise<INodeExecutionData> { console.log(`Prompts="${prompts.join(', ')}"`) } }
已尝试的方案
错误原因是.call(thisArgs)覆盖了execute函数内的this上下文。试过把execute改成箭头函数,但这样就访问不到thisArgs了。
我的需求是:有没有办法同时访问类实例的this和.call()传入的thisArgs?这样既能调用实现的抽象方法,又能使用thisArgs里的辅助函数。
解决方案
方案1:闭包绑定类实例的this
在抽象类构造函数中保存类实例的this到闭包变量,execute内用该变量调用类方法,同时用当前this访问thisArgs:
export abstract class AbstractNode<TParams> implements Omit<INodeType, 'description'> { private _executeFunctions: IExecuteFunctions = null private readonly _instanceThis: this constructor() { // 保存类实例的this到闭包 this._instanceThis = this } set executeFunctions(value: IExecuteFunctions) { this._executeFunctions = value } get executeFunctions(): IExecuteFunctions { return this._executeFunctions } abstract run(t: TParams): Promise<INodeExecutionData> async execute(): Promise<INodeExecutionData[][]> { // 当前this是call传入的thisArgs this.executeFunctions = this as unknown as IExecuteFunctions // 用保存的_instanceThis调用类方法 await this._instanceThis.run({ prompts: ['hello', 'world'] } as TParams) return this.executeFunctions.prepareOutputData([ { json: { answer: 'Sample answer' } }, ]) } }
方案2:利用调用时的参数传递
观察n8n调用代码,它会把thisArgs作为参数传入execute,因此可以直接通过参数访问thisArgs,同时提前把execute绑定到类实例:
// 修改n8n调用逻辑,先绑定execute到类实例 async function run(): Promise<void> { const myClass = loadClassInIsolation<AbstractNode<unknown>>( '../dist/codex/node/Codex.node.js', 'Codex', ) // 绑定execute到类实例,确保this指向类本身 myClass.execute = myClass.execute.bind(myClass) const thisArgs = { prepareOutputData: (d: any): any => ({ ...d }), } // 直接传入thisArgs作为参数 console.log(await myClass.execute(thisArgs)) }
修改抽象类的execute方法:
async execute(executeContext: IExecuteFunctions): Promise<INodeExecutionData[][]> { this.executeFunctions = executeContext // 此时this是类实例,可正常调用run await this.run({ prompts: ['hello', 'world'] } as TParams) return this.executeFunctions.prepareOutputData([ { json: { answer: 'Sample answer' } }, ]) }
方案3:用Symbol保存实例引用
通过唯一Symbol给实例添加自身引用,execute内通过该Symbol获取类实例:
// 定义唯一Symbol标识 const INSTANCE_REF = Symbol('instanceRef') export abstract class AbstractNode<TParams> implements Omit<INodeType, 'description'> { private _executeFunctions: IExecuteFunctions = null constructor() { // 给实例添加Symbol属性指向自身 (this as any)[INSTANCE_REF] = this } set executeFunctions(value: IExecuteFunctions) { this._executeFunctions = value } get executeFunctions(): IExecuteFunctions { return this._executeFunctions } abstract run(t: TParams): Promise<INodeExecutionData> async execute(): Promise<INodeExecutionData[][]> { this.executeFunctions = this as unknown as IExecuteFunctions // 通过Symbol获取类实例 const instance = (this as any)[INSTANCE_REF] await instance.run({ prompts: ['hello', 'world'] } as TParams) return this.executeFunctions.prepareOutputData([ { json: { answer: 'Sample answer' } }, ]) } }
内容的提问来源于stack exchange,提问作者Natan Deitch
相关产品推荐
相关产品推荐

