购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.6 学会调试Nest应用

调试能力是衡量一个开发者水平的重要指标。在编写应用时,首先应该了解如何调试应用程序。通过调试,我们可以清晰地看到程序在运行过程中的具体执行状态。

本节将通过调试请求过程和异常信息来演示如何在VS Code中调试Nest。

图2-46 创建nest-debug项目

2.6.1 Chrome DevTools调试

首先,运行nest n nest-debug命令创建一个项目,如图2-46所示。

在项目中运行nest start--watch–debug命令,可以启动应用并开启热重载以及调试模式,运行成功后,效果如图2-47所示。

图2-47 控制台上的运行结果

访问http://localhost:3000/,可以看到正常返回的内容,如图2-48所示。

图2-48 浏览器上的运行结果

除此之外,Nest启动了一个如图2-47所示的端口为9229的WebSocket服务。我们只需要连接这个端口就可以进行调试了。在Chrome中输入chrome://inspect后按Enter键,显示已经连接上远程调试目标,连接的正是nest-debug这个项目,如图2-49所示。

图2-49 连接调试目标

其中,Configure是用来配置端口号的。我们可以设置调试服务的端口,Nest中默认是9229,如图2-50所示。

图2-50 默认9229端口

当然,可以在启动服务时修改端口号,比如输入nest start --watch --debug 8083,此时启动的就是8083端口,如图2-51所示。然后在Configure中配置即可连接。

图2-51 自定义调试端口

在图2-49中单击inspect,导入项目文件夹,在app.Controller.ts的getHello()方法中添加一个断点。刷新页面之后,可以看到代码自动在断点处停下,如图2-52所示。

图2-52 断点演示效果

这样就完成了使用Chrome浏览器调试Nest应用的过程。然而,这种方法可能不太方便。我们通常会使用VS Code等集成开发环境(IDE)进行开发。那么,能否直接通过这些工具进行调试呢?答案是肯定的。接下来将详细讲解。

2.6.2 VS Code调试

在终端窗口通过命令“nest start --watch –debug”启动调试服务,如图2-53所示。

图2-53 启动调试服务

接着在需要调试的地方打个断点,如图2-54所示。

图2-54 在指定行打断点

然后在http://localhost:3000/地址刷新浏览器,此时程序会直接在断点处停下,如图2-55所示。

图2-55 断点效果

这种方式是否比之前的方法更简单呢?确实如此。无论是Chrome DevTools还是VS Code等第三方集成开发环境(IDE),它们都通过CDP(Chrome DevTools Protocol)协议实现了调试服务的功能。

有些读者可能会提出这样的问题:我不想每次都手动输入--debug参数,能否在执行“yarn start:dev”命令后自动进入调试模式?

当然可以。你只需在package.json文件中添加一个脚本(script)命令即可实现这一点。在Nest项目中,这个脚本默认就已经存在了,如图2-56所示。

图2-56 package.json中的scripts配置

确实,使用Nest CLI的--debug参数可以方便地启动调试模式。但如果是其他Node.js脚本,情况就不同了。这正是笔者想要提到的:VS Code提供了一个便捷的自动附加模式。

要使用这个功能,你可以通过按快捷键Command+Shift+P(在Windows中)或Ctrl+Shift+P(在Linux中),然后在弹出的命令面板中输入“Toggle Auto Attach”,接着选择“总是”选项,以自动附加调试器到Node.js进程,如图2-57所示。

图2-57 VS Code自动附加

重新打开控制台并运行“yarn start:dev”命令,此时VS Code在启动应用的同时,也会自动创建一个调试器(debugger)服务。该调试服务的默认端口号为61174,如图2-58所示。

图2-58 附加模式启动效果

重新在浏览器中访问http://localhost:3000/,依然可以进入断点调试状态,如图2-59所示。

图2-59 调试效果

以上两种方式会在每次执行命令时创建一个新的WebSocket调试服务。然而,对于一些开发者来说,这种方法可能并不适合他们的需求。接下来,在2.6.3节中,我们将探讨一些扩展的调试技巧。

2.6.3 扩展调试技巧

前面讲解了基于--debug参数来启动调试服务,如图2-60所示。从CLI源码中可以看到,本质上还是执行“node xxxx –inspect”命令来运行的。

图2-60 --debug参数的定义

因此,我们也可以选择在VS Code中使用这种方式进行调试。首先,在调试面板中单击创建launch.json文件的选项,如图2-61所示。

图2-61 创建launch.json调试文件

然后选择Node.js作为调试器,如图2-62所示。

图2-62 选择Node.js调试器

接着根据需要配置相关的调试信息。在launch.json文件中,将program字段的值修改为你的应用程序的入口点,例如main.ts:

配置完成之后,在app.service.ts的getHello()方法中添加断点,单击左上角的“启动调试”按钮,如图2-63所示。

图2-63 打断点并启动调试

可以看到在“调用堆栈”中新增了一个启动程序,这就是VS Code调试器。我们依旧在Chrome中访问http://localhost:3000/,此时程序自动在断点处停住,并且在左侧可以看到调用栈信息和变量情况,如图2-64所示。

图2-64 调试调用栈情况

这种方法的优势在于它避免了每次启动都需要创建一个新的WebSocket调试服务,从而节省了运行时的内存消耗。调试程序仅在需要时启动,这正是我们最常用的调试方式。它适用于前端应用,如Vue、React以及Node.js等多种应用的调试。 HPpkPD0LLzq9SfvkyujV5JUFQ2Fc0pVePrpSO+oKJiPLuKcVlfj2EoEBRF+Ieloz



第3章
Nest核心概念介绍

本章将介绍Nest核心概念及其应用场景。首先,探讨贯穿Nest开发过程的装饰器,以及实现功能模块化的模块概念。接着,介绍管理路由的控制器,以及实现复杂业务逻辑并与数据层交互的服务。最后,介绍AOP架构下的中间件机制和拦截器功能。读者可结合实际代码示例,全面了解这些概念在实际开发中的应用。 DfGOQoagPoZpgSy3nDbYBo2RukoK0j3lLWtl+aTS70Lq2qX2oEbV78siVeW0jr1E



3.1 贯穿全书的装饰器

本节首先介绍装饰器(Decorator)的基本概念和常见的5种装饰器类型,包括类装饰器、方法装饰器、属性装饰器、参数装饰器和访问器装饰器。接着介绍Nest中常用的装饰器,我们先初步认识它们。

3.1.1 基本概念

顾名思义,装饰器(Java中有个类似的概念叫注解)是用来装饰和扩展对象功能的。它能够在不改变原有对象结构的前提下,增加额外的功能,以满足更多的实际需求。

举个例子:一间房子里放了一张床,就满足了基本的居住需求。在此基础上,如果新增了沙发、红酒杯、电视机,那么就不仅能够满足休息的需求,还能提供娱乐和休闲的体验。在这个比喻中,沙发、红酒杯、电视机就像是装饰器,它们可以在不影响基本功能的前提下,根据需要随意添加或移除,从而实现功能的扩展和松耦合。

3.1.2 装饰器的种类

上例中,沙发、红酒杯、电视机属于不同的装饰器,它们实现了不同的功能。同样地,常用的装饰器也分为以下几种:

(1)类装饰器。

(2)方法装饰器。

(3)属性装饰器。

(4)参数装饰器。

(5)访问器装饰器。

尽管装饰器仍处于提案(stage-3)阶段,但它们已经广泛应用于TypeScript和Babel编译中。以方法装饰器为例,在stage-3提案中的实现如下:

上述代码中定义了一个Log方法装饰器,用于修饰类方法,其中target是被修饰的对象,context提供上下文信息。在基于TypeScript构建的Nest项目中,采用的是stage-1提案的装饰器版本,具体如下:

     /**
       * 方法装饰器
       */
     const log: MethodDecorator = (
       target: Function,
       propertyKey: string | Symbol,
       descriptor: PropertyDescriptor
     ) => {
       // 此处编写你的逻辑
     };

上述代码中,Log方法装饰器接收3个参数:target是被修饰的值,propertyKey是被修饰的属性键(方法名),description是被装饰的方法的属性描述符对象。

下面通过简单的示例分别进行介绍。

1.类的装饰

装饰器可以用来装饰整个类,以此增强类的功能,示例代码如下:

在上面的代码中,@doc用来装饰App类,同时往原型链上添加一个属性name,target是被修饰的类。

我们通过CodePen来编译ES6代码。CodePen是一个社区驱动的在线代码编辑器,支持编辑HTML、CSS和JavaScript。CodePen还支持Babel,这使得它能够将ES6代码转换成ES5并进行实时预览。

由于本节案例中我们使用TypeScript编码,因此需要在CodePen中找到Settings下的JS选项,并将JavaScript Preprocessor(JavaScript预处理器)设置为TypeScript,如图3-1所示。

图3-1 设置CodePen预处理器

保存后,类装饰器执行结果如图3-2所示。

图3-2 类装饰器的执行结果

上面打印了代码预期的结果,成功地给实例添加了属性name,并获取了App的构造函数,这样我们就可以动态地修改类的属性和行为了。

如果觉得一个target参数不够用,怎么办呢?可以通过工厂函数来增强装饰器的能力。代码如下:

通过工厂函数增强装饰器的能力后,@doc可以接收外部参数,使得App被实例化时能够获取更多的信息来组织代码,具体效果如图3-3所示。

图3-3 装饰器工厂

至此,你应该已经理解了Nest中的装饰器的实现方式,它在依赖注入中扮演着重要的角色。然而,请不要急于下结论,让我们继续深入探讨。

2.方法的装饰

装饰器可以用来装饰类的方法,代码如下:

在上面的代码中,@Log装饰器函数修饰了User类的getName方法。其中,target是被“装饰”的类的原型,即User.prototype。由于此时类还没有被实例化,因此只能装饰原型对象。这与类装饰器有所不同。运行结果如图3-4所示。

图3-4 方法装饰器的运行结果

由于User类的原型尚未附加属性方法,因此是空对象{}。

3.属性的装饰

装饰器也可以用来装饰类的属性,代码如下:

在上面的代码中,@prop用于装饰User类的name属性。此时,target是被装饰的类的原型对象,propertyKey是被修饰的属性名。具体的运行结果如图3-5所示。

图3-5 属性装饰器的运行结果

4.参数的装饰

除了装饰类、类方法和类属性外,装饰器还能装饰类方法的参数,示例代码如下:

在上述代码中,@Param用于装饰类方法getName的参数,其中target是被装饰类的原型,propertyKey是被装饰的类方法,index是装饰方法参数的索引位置。具体的运行结果如图3-6所示。

图3-6 参数装饰器的运行结果

5.访问器的装饰

装饰器还可以用来修饰类的访问器,即属性的getter方法。示例代码如下:

访问器装饰器与类方法装饰器类似,唯一的区别在于它们的描述符(descriptor)中某些键(key)不同。

方法装饰器的描述器键包括:

● value

● writable

● enumerable

● configurable

访问器装饰器的描述器的key为:

● get

● set

● enumerable

● configurable

运行结果如图3-7所示。

图3-7 访问器装饰器的运行结果

了解了这几个装饰器的概念后,接下来学习Nest中的装饰器,你应该能够轻松理解。

3.1.3 Nest中的装饰器

在Nest中实现了前面列举的前4种装饰器,它们包含但不限于:

● 类装饰器:@Controller、@Injectable、@Module、@UseInterceptors。

● 方法装饰器:@Get、@Post、@UseInterceptors。

● 属性装饰器:@IsNotEmpty、@IsString、@IsNumber。

● 参数装饰器:@Body、@Param、@Query。

下面分别对它们进行解释。

● @Controller():用于装饰控制器类,使之能够管理应用中的路由程序,并通过设置路由路径前缀来模块化管理路由。

● @Injectable():装饰后成为服务提供者,可以被其他对象进行依赖注入。

● @Module():模块装饰器,用于在Nest中划分功能模块并限制依赖注入的范围。

● @UseInterceptors():用于绑定拦截器,将拦截器的作用范围限制在控制器类范围中(当然也可以作用在类方法上)。

● @Get、@Post:用于定义路由方法的HTTP请求方式。

● @IsNotEmpty、@IsString、@IsNumber:用于在参数的验证场景中校验HTTP请求的参数是否符合预期。

● @Body、@Param、@Query:用于接收HTTP请求发送的数据,不同的请求方式对应不同的参数接收方式。

Nest中的装饰器主要分为这几类。理解了它们将有助于我们在使用过程中更加得心应手。关于这些装饰器的具体使用方法,我们将在后续章节中逐一进行详细介绍。 DfGOQoagPoZpgSy3nDbYBo2RukoK0j3lLWtl+aTS70Lq2qX2oEbV78siVeW0jr1E

点击中间区域
呼出菜单
上一章
目录
下一章
×