javascript 避免多次按下回车按钮时的多个http调用

kyxcudwk  于 2023-01-07  发布在  Java
关注(0)|答案(5)|浏览(135)

我有一个按钮,它可以在键盘输入时调用API。如果他们立即多次输入,它会调用“n”号。
如何用一个干净的通用解决方案来避免这一点,以便它可以在任何地方使用?

<button click="initiateBulkPayment()(keyup.enter)="initiateBulkPayment">

    initiateBulkPayment = (orderId:any, payment_methods:any) => {
       let postParams = payment_methods;
       console.log('this is payment_method', payment_methods);

       return this.http.post(Constants.API_ENDPOINT + '/oms/api/orders/' + 
           orderId + '/payments/bulk_create/', postParams, this.auth.returnHeaderHandler())
          .pipe(map((data: any) => {
           return data;
       }),
       catchError((err)=>{
         return throwError(err);
       }));
   }
w7t8yxp5

w7t8yxp51#

有两种解决方案:
1.在执行呼叫时禁用此按钮
1.跳过多余呼叫

    • 在执行呼叫时禁用此按钮:**
<button [disabled]="paymentRequest.inProgress$ | async" (click)="onPayButtonClick()">
export class ProgressRequest {
    private _inProgress$ = new UniqueBehaviorSubject(false);

    execute<TResult>(call: () => Observable<TResult>): Observable<TResult> {
        if (!this._inProgress$.value) {
            this._inProgress$.next(true);
            return call().pipe(
                finalize(() => {
                    this._inProgress$.next(false);
                })
            );
        } else {
            throw new Error("the request is currently being executed");
        }
    }

    get inProgress$(): Observable<boolean> {
        return this._inProgress$;
    }
}

@Component({ ... })
export class MyComponent {
    readonly paymentRequest = new ProgressRequest();

    onPayButtonClick() {
        this.paymentRequest.execute(() => {
            return this.http.post(
                Constants.API_ENDPOINT + '/oms/api/orders/' + orderId + '/payments/bulk_create/',
                postParams,
                this.auth.returnHeaderHandler()
            ).pipe(map((data: any) => {
                return data;
            });
        }).subscribe(data => {
            console.log("done!", data);
        });
    }
}
    • 跳过多余呼叫:**

您可以使用exhaustMap跳过正在执行的请求。注意,switchMapshareReplay(在其他答案中建议)不会阻止过多的http调用。
一个二个一个一个
请注意,当您按下enter键时,click事件也会被触发,因此没有必要侦听"keyup"。

// You can replace
merge(
    fromEvent(this.paymentButton.nativeElement, 'click'),
    fromEvent<KeyboardEvent>(this.paymentButton.nativeElement, 'keyup').pipe(
        filter(event => event.key === "Enter")
    )
)

// just by
fromEvent(this.paymentButton.nativeElement, 'click')
z18hc3ub

z18hc3ub2#

我能想到的最独立的方法是使用一个指令来扩展按钮元素的功能。
其思想是,按钮可以将其单击事件Map到内部流,并忽略所有后续的单击事件,直到内部流完成。
这可以通过以下方式实现:

import { Directive, ElementRef, AfterViewInit, Input } from '@angular/core';
import { Observable, isObservable, of, fromEvent, Subscription, empty } from 'rxjs';
import { exhaustMap, tap, take, finalize } from 'rxjs/operators';

export type ButtonHandler = (e?: MouseEvent) => Observable<unknown> | Promise<unknown>;

const defaultHandler: ButtonHandler = (e) => empty();

@Directive({
  selector: 'button[serial-btn]',
  exportAs: 'serialBtn',
  host: {
    '[disabled]': 'disableWhenProcessing && _processing'
  }
})
export class SerialButtonDirective implements AfterViewInit {
  private _processing = false;
  private _sub = Subscription.EMPTY;

  @Input()
  disableWhenProcessing = false;

  @Input()
  handler: ButtonHandler = defaultHandler;

  get processing(): boolean { return this._processing };

  constructor(private readonly btnElement: ElementRef<HTMLButtonElement>) {
  }

  ngAfterViewInit() {
    this._sub = fromEvent<MouseEvent>(this.btnElement.nativeElement, 'click')
      .pipe(
        exhaustMap(e => this.wrapHandlerInObservable(e))
      ).subscribe();
  }

  ngOnDestroy() {
    this._sub.unsubscribe();
  }

  private wrapHandlerInObservable(e: MouseEvent) {
    this._processing = true;
    const handleResult = this.handler(e);
    let obs: Observable<unknown>;
    if (isObservable(handleResult)) {
      obs = handleResult;
    } else {
      obs = of(handleResult);
    }
    return obs.pipe(take(1), finalize(() => this._processing = false));
  }
}

您可以将其用作:

<button serial-btn [handler]="handler">Handle</button>

import {timer} from 'rxjs';
import {ButtonHandle} from './directive-file';

handler: ButtonHandler = (e) => {
    console.log(e);
    return timer(3000);
  }

现场演示可在this stackblitz中找到

5vf7fwbs

5vf7fwbs3#

您可以添加一个指令,在给定的时间内禁用该按钮。

// debounce.directive.ts
import { Directive, OnInit, HostListener, ElementRef, Input } from '@angular/core';

@Directive({
  selector: '[appDebounce]'
})
export class DebounceDirective {
  constructor(
    private el: ElementRef<HTMLButtonElement>,
  ) { }

  @Input() appDebounce: number;

  @HostListener('click') onMouseEnter() {
    this.el.nativeElement.disabled = true;
    setTimeout(() => this.el.nativeElement.disabled = false, this.appDebounce)
  }
}

// component.ts
<button [appDebounce]="1000" click="initiateBulkPayment()(keyup.enter)="initiateBulkPayment">

查看此live demo

0tdrvxhp

0tdrvxhp4#

先做一个属性指令,也可以设置阻塞的时间。

@Directive({
          selector: '[preventMultipleCalls]'
        })
        export class PreventApiCallsDirective implements OnInit,OnDestroy {
        
          @Input('time') throttleTimeValue = 10000 
          @Output('emit') fireCallEventEmitter :EventEmitter<boolean>= new EventEmitter<boolean>();
          clickObservable = new Observable<Event>();
          clickSubscription: Subscription;
        
          constructor(private elementRef: ElementRef) {
            this.clickObserable = fromEvent(this.elementRef.nativeElement,'keyup')
           }
        
          ngOnInit(): void {
            this.clickSubscription = this.clickObserable.pipe(filter((e :any) => e.keyCode === 13),
               throttleTime(this.throttleTimeValue))
              .subscribe(event => {
                this.fireCallEventEmitter.emit(true)
              });
          }
        
          ngOnDestroy(): void {
            this.clickSubscription?.unsubscribe();
          }
        
        }

然后在按钮上放置如下指令:

<button preventMultipleCalls (emit)="initiateBulkPayment()"> </button>
6qftjkof

6qftjkof5#

您可以禁用/启用按钮以防止单击事件或使用shareReplay rxjs操作符

return this.http.post(Constants.API_ENDPOINT + '/oms/api/orders/' + 
         orderId + '/payments/bulk_create/', postParams, 
         this.auth.returnHeaderHandler())
       .pipe(map((data: any) => {
               return data;
            }
        ),
        shareReplay(1)
        catchError((err)=>{
          return throwError(err);
        }));

文档链接:https://www.learnrxjs.io/operators/multicasting/sharereplay.html

相关问题