---
title: "防抖和竞态问题"
date: "2026-02"
category: "随笔"
tags: ["随笔"]
description: "防抖很重要，尤其是在前后端有网络IO的情况下，如果用户每次操作都要往后端发一个请求，那流量费和后端服务器压力就会很大。标准处置方法就是加个延迟，用户在这个时间段内的连续操作..."
source: "https://enriquemark.com/zh-hans/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` 就是用来解决这个问题，如果数值发生变动，则上一次的请求就会直接取消，永远只取最后一次的结果。
