---
title: "防抖和競態問題"
date: "2026-02"
category: "隨筆"
tags: ["隨筆"]
description: "防抖很重要，尤其是在前後端有網路IO的情況下，如果使用者每次操作都要往後端發一個請求，那流量費和後端伺服器壓力就會很大。標準處置方法就是加個延遲，使用者在這個時間段內的連續操作..."
source: "https://enriquemark.com/zh-hant/posts/%E9%98%B2%E6%8A%96%E5%92%8C%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98"
---

防抖很重要，尤其是在前後端有網路IO的情況下，如果使用者每次操作都要往後端發一個請求，那流量費和後端伺服器壓力就會很大。標準處置方法就是加個延遲，使用者在這個時間段內的連續操作，只取最後一次（或者是最終結果）。
防抖這方面，Angular的RxJS庫裡面有搭配的方法：

```ts
// 其他引入省略
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({ ... })
export class SearchComponent {
  // 1. 创建一个表单控制器，它本身就是一个 Observable 源
  searchControl = new FormControl('');
  // 2. 定义结果流
  // 如果如果 Observable 是水管，这里的.pipe就可以理解成一个过滤器
  // 进去之后进行一堆加工，最后的输出处理之后的结果
  // valueChanges 负责监听值变化事件，若有则开始加工
  users$ = this.searchControl.valueChanges.pipe(
    // --- 管道开始 ---
    // 注意下面三个是任选的，其实功能有重合，不必要全加进去
    
    // 防抖：300ms内如果用户还在打字，就不处理
    debounceTime(300),
    
    // 去重：如果用户输入 "A" -> 删掉变成 "" -> 又输入 "A"，
    // 值没变，就不发请求
    distinctUntilChanged(),
    
    // SwitchMap
    // 处理竞态问题，一旦有新值过来，立刻取消上一次还没完成的 HTTP 请求
    // 永远只保留最后一次请求的结果
    switchMap(term => {
      // 如果为空，返回空数组，不发请求
      if (!term) return []; 
      // 发起新的请求
      return this.http.get<User[]>(`/api/search?q=${term}`);
    })
  );

  constructor(private http: HttpClient) {}
}
```

競態問題，具體來說就是因為網路波動，導致使用者傳送的結果載入時序錯亂，導致當下請求被後返回的先前資料覆蓋的情況發生。`switchMap` 就是用來解決這個問題，如果數值發生變動，則上一次的請求就會直接取消，永遠只取最後一次的結果。
