【问题标题】:Angular HttpClient Get Service Returning Too EarlyAngular HttpClient 让服务过早返回
【发布时间】:2019-01-08 12:59:08
【问题描述】:

我正在尝试将旧的 Ionic Angular Http 代码转换为新的 Httpclient 格式,但 Get 服务过早将控制权返回给调用函数,因此它没有获取返回的数据。

我尝试过使用 async/await,但它对控制流没有影响。

我是 observables 的新手,所以我确定这是我做的不对,但我不知道是什么。

这些是我的代码中的函数,具有 getAsJson 函数的新格式,使用 Httpclient 的订阅功能。我只想从http调用返回数据,所以我没有在options参数中包含“observe:'response'”。

loadLoyaltyDetails 调用 getLoyaltyDetails,它会执行除此处所示之外的其他一些操作,然后调用 getAsJson。

功能:

loadLoyaltyDetails(): Promise<void> {
  return this.loyalty.getLoyaltyDetails().then(data => {
    console.log("in loadLoyaltyDetails, data =", data);
  })
  .catch(this.handleServiceLoadingError.bind(this))
}

getLoyaltyDetails(): Promise<LoyaltyDetails> {
  return this.callApi.getAsJson("/loyalty/url");
}

getAsJson(path: string): Promise<any> {
  let opts = this.getRequestOptions();
  console.log("in getAsJson, path = ", path);

  return this.http.get<HttpResponse<HttpEventType.Response>>(`${environment.API_URL}${path}`, opts)
    .subscribe(
      (res) => {
        console.log("in getAsJson, res = ", res);
        return res;
      },
      (err) => {
        console.log("in getAsJson, err = ", err);
        this.checkResponseForUnauthorized(err);
      }
    )
}

控制台日志消息

in getAsJson, path = /loyalty/url

in loadLoyaltyDetails, data = 
Object { closed: false, _parent: null, _parents: null, _subscriptions: (1) […], syncErrorValue: null, syncErrorThrown: false, syncErrorThrowable: false, isStopped: false, destination: {…} }

Using current token

in getAsJson, path = /other/url

in getAsJson, res = {…}
    endDate: "2019-01-08"
    numberOfShiftsRequired: 18
    numberOfShiftsWorked: 0
    startDate: "2019-01-08"

in getAsJson, res = Array []

如日志消息所示,loadLoyaltyDetails 首先调用 getAsJson,但立即得到一堆 gobbledegook。然后 getAsJson 被另一个函数调用,接收第一次调用的数据,然后是第二次调用。

我希望在返回第一组数据后出现'in loadLoyaltyDetails, data =' 行。

这是我无法弄清楚的,即如何确保在返回数据之前控制权不会返回给 loadLoyaltyDetails?

【问题讨论】:

    标签: angular ionic3 rxjs angular-httpclient


    【解决方案1】:

    subscribe 函数会立即返回一个Subscribtion 对象,并且在订阅的 observable 实际发出值之前不会暂停代码的执行。 Subscribtion 对象不用于从 Observable 获取数据,而仅用于取消订阅您之前订阅的 Observable(请注意,您不必取消订阅由 HttpClient 返回的 Observable,因为它们已完成并因此取消订阅自动地)。

    通过调用return this.http.get(..).subscribe(..),您将这个(对您无用的)订阅对象一直返回到您的loadLoyaltyDetails() 函数,您将其记录为data 对象。

    相反,您应该返回 Observables,直到您真正需要 Observable 中的数据为止(我想这对您来说是 loadLoyaltyDetails())。这是您订阅的地方,在 subscribe 函数中,您可以对 Observable 发出的对象(在您的情况下为 http 响应正文)执行您需要做的事情。 通常,您会将在 html 模板中显示的一些组件变量设置为 Observable 发出的值。您甚至可以使用 AsyncPipe 推迟对您的模板的订阅,并且根本不用手动订阅。

    如果您不需要处理完整的 HttpResponse,而只想获取 JSON 正文并处理错误,您可以执行以下操作:

    localLoyaltyDetails: LoyaltyDetails;
    
    // Note that this function returns nothing especially no Subscribtion object
    loadLoyaltyDetails(): void {
      // supposing this is where you need your LoyaltyDetails you subscribe here
      this.loyalty.getLoyaltyDetails().subscribe(loyaltyDetails => {
        // handle your loyaltyDetails here
        console.log("in loadLoyaltyDetails, data =", loyaltyDetails);
        this.localLoyaltyDetails = loyaltyDetails;
      });
    }
    
    getLoyaltyDetails(): Observable<LoyaltyDetails> {
      // We call getAsJson and specify the type we want to return, in this case 
      // LoyaltyDetails. The http response body we get from the server at the given url, 
      // in this case "/loyalty/url", has to match the specified type (LoyaltyDetails).
      return this.callApi.getAsJson<LoyaltyDetails>("/loyalty/url");
    }
    
    // Don't subscribe in this function and instead return Observables up until the 
    // point where you actually need the data from the Observable.
    // T is the type variable of the JSON object that the http get request should return.
    getAsJson<T>(path: string): Observable<T> {
      let opts = this.getRequestOptions(); 
      console.log("in getAsJson, path = ", path);
    
      return this.http.get<T>(`${environment.API_URL}${path}`, opts)
        .pipe(
          // you could peek into the data stream here for logging purposes 
          // but don't confuse this with subscribing
          tap(response => console.log("in getAsJson, res = ", response)),
          // handle http errors here as this is your service that uses the HttpClient
          catchError(this.handleError) 
        );
    }
    
    // copied from the Angular documentation
    private handleError(error: HttpErrorResponse) {
      if (error.error instanceof ErrorEvent) {
        // A client-side or network error occurred. Handle it accordingly.
        console.error('An error occurred:', error.error.message);
      } else {
        // The backend returned an unsuccessful response code.
        // The response body may contain clues as to what went wrong,
        console.error(
          `Backend returned code ${error.status}, ` +
          `body was: ${error.error}`);
      }
      // return an observable with a user-facing error message
      return throwError(
        'Something bad happened; please try again later.');
    };
    

    您可以在Angular HttpClient Docs 中阅读有关HttpClienthandleError 函数的更多信息。您还可以编写一个handleError 函数,该函数返回一个错误的默认值,例如Angular Tutorial (Http Error Handling) 中的错误。


    编辑您的评论:

    使用 defer 函数从您的 Promise 生成一个 Observable(Observable 的生成以及 Promise 的执行都被推迟到订阅者实际订阅了 Observable 之后)。

    import { defer } from 'rxjs';
    
    // defer takes a Promise factory function and only calls it when a subscriber subscribes 
    // to the Observable. We then use mergeMap to map the authToken we get from  
    // getLoggedInToken to the Observable we get from getAsJson.
    getLoyaltyDetails(): Observable<LoyaltyDetails> {
      return defer(this.login.getLoggedInToken)
        .pipe(
          mergeMap(authToken =>
            this.callApi.getAsJson<LoyaltyDetails>(authToken, "/loyalty/details/NMC")
          )
        );
    }
    

    请注意,loadLoyaltyDetails 不返回任何内容,即void

    private details: LoyaltyDetails;
    
    loadLoyaltyDetails(): void {
      // supposing this is where you need your LoyaltyDetails you subscribe here
      this.loyalty.getLoyaltyDetails().subscribe(loyaltyDetails => {
        console.log("in loadLoyaltyDetails, data =", loyaltyDetails);
    
        // set a local variable to the received LoyaltyDetails
        this.details = loyaltyDetails;
      });
    }
    

    由于您的 loadLoyaltyDetails 不返回任何内容,您只需在需要执行该函数时调用该函数。

    this.loader.wraps<void>(
      this.loadShifts().then(() => this.loadLoyaltyDetails())
    );
    

    【讨论】:

    • 谢谢@fridoo。我想我现在更好地理解了关卡之间的交互。但是,我不确定 的用途。当我尝试将它们如上所述放入我的 IDE 时,它显示“找不到名称 T”,当我尝试使用 HttpResponse 时,它​​一直抱怨 Type Observable> 不可分配给类型 >。我需要 getAsJson 是一个通用的 http 函数,而不是一个绑定到特定类型的函数。我怎样才能做到这一点?
    • @AlphaBeta T 是一个类型变量,它是特定给定类型的占位符(请参阅:typescriptlang.org/docs/handbook/generics.html)。我使用了泛型,以便 getAsJson 不绑定到特定类型,并且可以调用以返回任何特定的给定类型。每当您调用 getAsJson 时,您现在必须指定您希望函数返回的类型,例如当你调用 getAsJson("myUrl") 你告诉 getAsJson 函数返回一个 Observable 对象。您可能会出错,因为您在没有指定返回类型的情况下在某处调用 getAsJson。
    • 感谢澄清,我认为可能是这样的。但是,在整个早上挣扎之后,我仍然收到 getLoyaltyDetails 错误。我使用的代码是:getLoyaltyDetails(): Observable { return this.login.getLoggedInToken().then(async (authToken) => { return this.callApi.getAsJson(authToken, "/loyalty /details/NMC") }); }(您可能必须将该代码复制到文本编辑器中,以便于阅读)
    • ...我得到的错误是:类型“Promise>”不可分配给类型“Observable”。我无法弄清楚它是从哪里获得 Promise 并阻止它这样做的。有什么想法吗?
    • 这已经消除了那个错误消息,但我现在在 loadLoyaltyDetails 级别有另一个。消息现在说:类型 'Subscription' 不可分配给类型 'Promise'。所以,一个问题是:当 getLoyaltyDetails 指定 LoyaltyDetails 的类型时,为什么它认为正在返回订阅?另一个是:我怎样才能在 loadLoyaltyDetails 中得到一个 Promise?
    猜你喜欢
    • 2015-12-28
    • 1970-01-01
    • 2021-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-22
    • 1970-01-01
    相关资源
    最近更新 更多