錯誤百科

資源在產生值之前就已完成

當以 Observable 為基礎的資源——rxResource 或 httpResource——在從未送出值或錯誤的情況下結束(完成)時,就會發生此錯誤。

資源最終一定要有_某種_結果:它載入的值,或說明出了什麼問題的錯誤。一個只是靜默完成的 Observable 兩者都不給 Angular,所以 Angular 不知道要把什麼放進資源裡,於是改拋出此錯誤。

使用 httpResource 時

httpResource 不會讓你自己撰寫 Observable,它會透過 HttpClient 在內部發出 HTTP 呼叫,並在回應傳回後完成。因此,如果你從 httpResource 看到此錯誤,請求本身不是問題所在,而是在它前面的某個東西在回應到達之前就把它吞掉了。

常見的嫌疑犯是攔截了錯誤卻沒有重新發出任何東西的 HttpInterceptor,這和下面 EMPTY 的錯誤相同,只是它出現在攔截器中而不是你自己的程式碼裡:

@Injectable()
class MyInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<unknown>, next: HttpHandler) {
    return next.handle(req).pipe(
      // BAD: same problem as `catchError(() => EMPTY)` in a `stream` —
      // the request completes with no response and no error, and any
      // httpResource depending on it hits NG0991.
      catchError(() => EMPTY),
    );
  }
}

修正方式與一般在攔截器管線中的做法相同:讓錯誤傳播出去(移除 catchError),或把它換成真正能解決請求的東西,例如 of(new HttpResponse({body: fallbackValue}))。

使用 rxResource 時

最常見的不慎造成方式,是攔截錯誤後用 EMPTY 把它吞掉:

import {rxResource} from '@angular/core/rxjs-interop';
import {EMPTY, catchError} from 'rxjs';

const userResource = rxResource({
  params: () => ({id: userId()}),
  stream: ({params}) =>
    this.http.get(`/users/${params.id}`).pipe(
      // BAD: this hides the error completely. The Observable just
      // completes with nothing, and the resource has no value and no
      // error to show for it — this is what triggers NG0991.
      catchError(() => EMPTY),
    ),
});

另一個常見的發生方式是:組合多個 Observable(使用 merge、race 等),其中一個可以在從未發出值之前自行完成,又沒有用 startWith(...) 保證初始值。如果所有組合的來源在全部完成之前沒有任何一個發出值,組合後的串流同樣會以空的狀態完成。

請挑選適合你情況的修正方式:

讓錯誤穿透過去。如果你不需要自己處理錯誤,只要移除 catchError 即可,資源會以它的 error 狀態結束,你已經可以用 userResource.error() 檢查它。

stream: ({params}) => this.http.get(`/users/${params.id}`),

用備援值復原。如果你寧願顯示某個東西而不是錯誤,請使用 catchError 傳回一個會發出值的 Observable,例如 of(...):

import {of, catchError} from 'rxjs';

stream: ({params}) =>
  this.http.get(`/users/${params.id}`).pipe(
    catchError(() => of(null)), // resource resolves with `null` instead of erroring
  ),

無論哪種方式,請確保你從 stream 傳回的 Observable 在完成之前,永遠至少發出一個值或一個錯誤。

你實際會在哪裡看到此錯誤

此錯誤不是在資源建立時拋出,也不一定在請求完成的地方拋出。它是在下一次有東西讀取資源值時才拋出,例如範本繫結中的 userResource.value()。這可能讓它看起來像是應用程式其他地方的錯誤,但真正成因在上游(stream 以空的狀態完成,或攔截器吞掉了回應)。

如果你的程式碼在讀取 .value() 之前沒有檢查資源的狀態,此錯誤可能會一路冒泡到 Angular 的全域 ErrorHandler,就像在渲染範本時拋出的任何其他未攔截錯誤一樣。

降低其影響的兩種方式,從最推薦到最不推薦:

  1. 從根本修正:使用上述其中一種方式,讓資源永遠不會進入這種狀態。
  2. 防護 .value() 的讀取:作為深度防禦,以防你無法完全控制的東西(例如第三方攔截器或服務)非預期地以空的狀態完成。在讀取 .value() 之前先檢查 .hasValue() 或 .status(),例如使用 @if (userResource.hasValue()) { {{ userResource.value() }} },而不是無條件地讀取 .value()。