NestJS interceptor - handle error inside .pipe()

Viewed 2474

I'd like to implement a NestJS-interceptor that creates and writes an elasticSearch entry before the request handler is hit and that updates this entry with error/success-info after the handler has finished. To do this I am using:

@Injectable()
export class ElasticsearchInterceptor implements NestInterceptor {

constructor(private readonly elasticSearchService: ElasticsearchService) {}

async intercept(_context: ExecutionContext, next: CallHandler): Promise < Observable < any >> {
    const elasticSearchPayload = new ElasticsearchPayloadBuilder()
        .setOperation(...)
        .build();
    const elasticSearchEntry = await this.elasticSearchService.writeEntry(elasticSearchPayload);

    return next
    .handle()
    .pipe(
        catchError(err => {
            elasticSearchPayload.status = err.status;
            return throwError(err);
        }),
        tap(() => {
            elasticSearchPayload.status = 'success';
        }),
        finalize(() => {
            this.elasticSearchService.updateEntry(elasticSearchEntry.id, elasticSearchPayload));
        }));
}

As long as the updateEntry-call resolves, this works fine, but in case that it fails, this results in an unhandled rejection. I'd like to make sure that the error is caught and thrown. I tried converting the updateEntry-promise into a new Observable using

finalize(() => {
    return from(this.elasticSearchService.updateEntry(elasticSearchEntry.id, elasticSearchPayload))
    .pipe(
        catchError(err => throwError(err)));
}));

but this does not solve the issue. How can I prevent the unhandled rejection and return the error from updateEntry?

1 Answers

finalize will simply invoke the provided callback during the teardown phase(e.g after complete, error from source or unsubscribe from consumer), which is why I think it is not working this way.

With that being said, this would be my approach:

const main$ = return next
  .handle()
  .pipe(
    catchError(err => {
        elasticSearchPayload.status = err.status;
        return throwError(err);
    }),
    tap(() => {
        elasticSearchPayload.status = 'success';
    }),
  );
const elastic$ = from(this.elasticService/* ... */).pipe(
  // might want to ignore elements and receive only errors
  ignoreElements(),

  catchError(err => throwError(err)),
);

return concat(main$, elastic$)
Related