You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore文档排序异常:先按未指定字段排序再按请求字段

问题描述

尝试使用以下查询语句按预订记录的from日期(Firestore中存储为Timestamp类型)对文档进行降序排序:

query(this.colRef,orderBy("from","desc"));

但结果不符合预期:显示的记录先按user字段排序,之后才按from日期排序,并非仅按指定的from字段排序。


前端展示代码(Angular PrimeNG)

<p-table [value]="bookings" dataKey="id"  [responsive]="true" [scrollable]="true" *ngIf="globals.windowWidth > 640">
<ng-template pTemplate="caption">
  <div class="table-header">
      <p-dropdown [options]="bookingYears" (onChange)="loadBookings()" [(ngModel)]="selectedYear" optionLabel="name"
                  optionValue="code" autoWidth="true" *ngIf="selectedYear!=undefined && !isShowingRequests"></p-dropdown>

      <button pButton pRipple type="button"  style="float: right;" icon="pi pi-refresh" (click)="loadBookings()" *ngIf="loadedBookings"></button>
      <button  icon="pi pi-spin pi-spinner" style="float: right;" class="p-button-rounded p-button-primary rightButton" pButton type="button" *ngIf="!loadedBookings"></button>
      <div></div>
      <br><br>
  </div>
</ng-template>
<ng-template pTemplate="header">
  <tr>
      <th *ngIf="bookerID == undefined">Booker</th>
      <th>From</th>
      <th>To</th>
      <th>Price</th>
      <th>Status</th>
      <th></th>
  </tr>

  <tr *ngIf="bookings.length == 0 && loadedBookings">
    <th colspan="6">
          No Bookings found!
    </th>
  </tr>
</ng-template>
<ng-template pTemplate="body" let-booking >
  <tr>
      <td *ngIf="bookerID == undefined">
        {{booking.user.forename}} {{booking.user.lastname}}
      </td>
      <td>
        {{booking.from.toLocaleDateString()}}
      </td>
      <td>
        {{booking.to.toLocaleDateString()}}
      </td>
      <td>
        {{bookingCalculationService.getTotalPrice(booking)}} €
      </td>
      <td >
        {{bookingsStatus[booking.status-1].name}}
      </td>
      <td style="text-align:center"><!--pi-window-maximize-->
        <button (click)="showBookingInfoDialog(booking)" icon="pi pi-window-maximize" pButton pRipple type="button"   class="p-button-rounded p-button-outlined" title="Edit"></button><!---->
      </td>
  </tr>
</ng-template>

TypeScript处理代码

getDocs(query)
  .then((snapchot) => {
    let counter = 0;
    if(snapchot.docs.length ==0 )this.loadedBookings = true;

    snapchot.docs.forEach((doc) => {
      this.fireStoreService.bookingConverter.fromFirestore(doc).then((booking: Booking) =>
      {
        if(this.isShowingRequests){
          if(this.authService.isLoggedInAndHost() && booking.status == 1)this.bookings.push(booking)
        }
        else if(this.isShowingBookingsAsHost){
          if(this.authService.isLoggedInAndHost() && booking.status != 1)this.bookings.push(booking)
        }
        else{
          if(this.authService.isLoggedIn())this.bookings.push(booking)
        }

        counter++;
        if(snapchot.docs.length == counter){
          this.loadedBookings = true;
        }
      });
    });
  })
  .catch((e)=>{
    console.log(e)
    this.showToastMessage('error', 'Error', 'An error orccured!');
  })
  ;

问题原因

Firestore查询返回的快照本身是按from字段降序排序的,但代码中使用forEach遍历快照文档时,每个文档的fromFirestore转换是异步操作(返回Promise)。异步操作的完成时间不固定,导致bookings数组中元素的顺序是异步回调完成的顺序,而非快照的原始排序顺序,这就造成了看起来像是按user字段排序的错觉。


解决方案

改用Promise.all等待所有文档转换完成,严格保持快照的排序顺序,之后再根据条件过滤并赋值给bookings数组:

getDocs(query)
  .then(async (snapchot) => {
    if(snapchot.docs.length === 0) {
      this.loadedBookings = true;
      return;
    }

    // 收集所有转换Promise,严格保留快照的原始顺序
    const bookingPromises = snapchot.docs.map(doc => 
      this.fireStoreService.bookingConverter.fromFirestore(doc)
    );
    
    // 等待所有转换完成,得到与查询结果顺序一致的booking数组
    const allBookings = await Promise.all(bookingPromises);
    
    // 根据条件过滤出需要展示的记录
    this.bookings = allBookings.filter(booking => {
      if(this.isShowingRequests){
        return this.authService.isLoggedInAndHost() && booking.status === 1;
      } else if(this.isShowingBookingsAsHost){
        return this.authService.isLoggedInAndHost() && booking.status !== 1;
      } else {
        return this.authService.isLoggedIn();
      }
    });

    this.loadedBookings = true;
  })
  .catch((e)=>{
    console.log(e);
    this.showToastMessage('error', 'Error', 'An error occurred!');
  });

关键改进点

  • 用map替代forEach,将每个文档的转换操作转为Promise数组,严格保留快照的排序
  • 通过Promise.all等待所有异步转换完成,确保最终数组顺序与Firestore查询结果一致
  • 统一过滤逻辑,直接生成符合条件的数组,避免逐个push导致的顺序混乱

内容的提问来源于stack exchange,提问作者ReneDeicker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:25:23