Angular2处理HTTP响应

71

我有一个问题,涉及到在服务中构建和处理Http请求的响应。我正在使用Angular2.alpha46 Typescript(刚开始测试它-我很喜欢它...顺便说一句,感谢所有通过Github做出贡献的人)

所以接下来看:

login-form.component.ts

import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
import {UserService} from '../../shared/service/user.service';
import {Router} from 'angular2/router';
import {User} from '../../model/user.model';
import {APP_ROUTES, Routes} from '../../core/route.config';

@Component({
    selector: 'login-form',
    templateUrl: 'app/login/components/login-form.component.html',
    directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
})

export class LoginFormComponent {
    user: User;
    submitted: Boolean = false;

    constructor(private userService:UserService, private router: Router) {
        this.user = new User();
    }

    onLogin() {
        this.submitted = true;

        this.userService.login(this.user,
            () => this.router.navigate([Routes.home.as]))
    }
}

从这个组件中,我导入了userService,它将包含我的登录用户的 HTTP 请求。该服务如下所示:

user.service.ts

import {Inject} from 'angular2/angular2';
import {Http, HTTP_BINDINGS, Headers} from 'angular2/http';
import {ROUTER_BINDINGS} from 'angular2/router';
import {User} from '../../model/user.model';

export class UserService {

    private headers: Headers;

    constructor(@Inject(Http) private http:Http) {
    }

    login(user: User, done: Function) {
        var postData = "email=" + user.email + "&password=" + user.password;

        this.headers = new Headers();
        this.headers.append('Content-Type', 'application/x-www-form-urlencoded');

        this.http.post('/auth/local', postData, {
                headers: this.headers
            })
            .map((res:any) => res.json())
            .subscribe(
                data => this.saveJwt(data.id_token),
                err => this.logError(err),
                () => done()
            );
    }

    saveJwt(jwt: string) {
        if(jwt) localStorage.setItem('id_token', jwt)
    }

    logError(err: any) {
        console.log(err);
    }
}
我想做的是能够处理HTTP请求返回的响应。例如,如果用户凭据无效,则我从后端传回401响应。我的问题是,最好的方法在哪里处理响应并将结果返回给调用该方法的组件,以便我可以操纵视图来显示成功消息或显示错误消息。
目前,在我的服务中,我当前没有处理响应,只是回调到原始组件,但我觉得这不是正确的方法?有人能否介绍一下在这种典型情况下他们会怎么做?我是否应该在subscribe函数的第一个参数中处理响应,例如:
 login(user: User, done: Function) {
     var postData = "email=" + user.email + "&password=" + user.password;

    this.headers = new Headers();
    this.headers.append('Content-Type', 'application/x-www-form-urlencoded');

    this.http.post('/auth/local', postData, {
            headers: this.headers
        })
        .map((res:any) => res.json())
        .subscribe(
            (data) => {
                // Handle response here
                let responseStat = this.handleResponse(data.header)

                // Do some stuff
                this.saveJwt(data.id_token);

                // do call back to original component and pass the response status
                done(responseStat);
            },
            err => this.logError(err)
        );
}

handleResponse(header) {
    if(header.status != 401) {
        return 'success'
    } 

    return 'error blah blah'
}

在这种情况下,使用回调函数可以吗?或者使用可观察对象或承诺会更好处理?

总的来说,我的问题是...在视图中从user.service.ts返回到login-form.component.ts,如何处理http响应并处理表单的状态,这是最佳实践是什么?


在下一个版本中(参见此提交),Http将在您获取非200状态代码时出错。目前,您可以在map()中处理响应,在那里检查状态并抛出错误或传递值。 - Eric Martinez
感谢您的迅速回复,Eric。太棒了!我也这么想,而且我知道有些事情仍在讨论中...所以只是确认一下...您是否认为现在在映射中处理响应状态并将值传递给回调完全可行?您能否用更好的方式来处理它,或者回调就足够了? - Nige
你根本不需要回调函数,看看我的答案,希望能解决你的问题 :D - Eric Martinez
非常棒,我从提交和 Plunker 中完全理解如何处理标头状态,但我只是想知道将错误或成功数据传回我调用该方法的原始组件的最佳方法,以便我可以将其分配给变量并在视图上进行绑定。你的例子很棒,但你是在该组件中调用 HTTP 请求,因此可以在同一组件中分配错误或成功数据,因为它们共享上下文;但是我该如何在服务中处理响应,然后在完成时传递响应和代码逻辑以成功或失败呢? - Nige
1
我会将那个逻辑从服务中移出。在服务中,我只会执行 return http.post().map(/* 处理结果 */), 然后在我的组件中,我会执行 this.userService.login(...).subscribe(/* 处理数据*/) - Eric Martinez
3个回答

88

更新 47版本

自47版本起,以下答案(适用于46版本及以下)已不再需要。现在Http模块会自动处理返回的错误。因此现在只需按照以下简单步骤操作即可。

http
  .get('Some Url')
  .map(res => res.json())
  .subscribe(
    (data) => this.data = data,
    (err) => this.error = err); // Reach here if fails

Alpha 46 及以下版本

您可以在subscribe之前在map(...)中处理响应。

http
  .get('Some Url')
  .map(res => {
    // If request fails, throw an Error that will be caught
    if(res.status < 200 || res.status >= 300) {
      throw new Error('This request has failed ' + res.status);
    } 
    // If everything went fine, return the response
    else {
      return res.json();
    }
  })
  .subscribe(
    (data) => this.data = data, // Reach here if res.status >= 200 && <= 299
    (err) => this.error = err); // Reach here if fails

这里有一个带有简单示例的plnkr

请注意,在下一版本中,这将不再是必需的,因为所有低于200和高于299的状态码都会自动引发错误,因此您无需自行检查它们。有关更多信息,请查看此提交


您链接的plnkr无法正常工作,请修复它。我需要在REST API请求时获取状态码,我该如何获取? - Pardeep Jain
@PardeepJain 你所说的“不工作”是什么意思?如果你想让请求失败,只需将第一个 get 注释掉,然后取消注释第二个。 - Eric Martinez
1
如果我想同时返回 res.status 和 res.json(),该怎么做? - Pardeep Jain
6
返回{status: res.status, json: res.json()}。 - Eric Martinez
1
我正在使用Angular2 v2.1.1,当服务器出现问题时,该方法不会触发错误部分。我在控制台中看到了由zone.js写出的异常。我需要拦截它以在UI中提供一些反馈。 - Sonic Soul
显示剩余3条评论

11

在Angular2 2.1.1中,我无法使用 (data),(error) 模式捕获异常,所以我使用 .catch(...) 实现了它。

这很好,因为它可以与所有其他 Observable 链式方法一起使用,例如 .retry .map 等。

import {Observable} from 'rxjs/Rx';


  Http
  .put(...)
  .catch(err =>  { 
     notify('UI error handling');
     return Observable.throw(err); // observable needs to be returned or exception raised
  })
  .subscribe(data => ...) // handle success

来自文档:

返回

(Observable): 包含连续源序列中的元素,直到源序列成功终止的可观察序列。


问题:可观察对象是否可以是其他不是错误的东西?否则,如果我再次抛出错误,那有什么意义呢? - Vic
1
不确定你的意思。observable 不是错误。通常它会携带可以订阅的数据对象。如果发生错误,它将触发 catch 块。在这种情况下,必须进行 throw,以便链条可以继续。其他订阅者可以决定如何处理它。例如,您可以在其他地方订阅此结果,他们可能希望以不同的方式处理错误。 - Sonic Soul
我现在明白了。那个throw关键字让我感到困惑。但是我现在看到它的好处了。谢谢。 - Vic

5

这项服务:

import 'rxjs/add/operator/map';

import { Http } from '@angular/http';
import { Observable } from "rxjs/Rx"
import { Injectable } from '@angular/core';

@Injectable()
export class ItemService {
  private api = "your_api_url";

  constructor(private http: Http) {

  }

  toSaveItem(item) {
    return new Promise((resolve, reject) => {
      this.http
        .post(this.api + '/items', { item: item })
        .map(res => res.json())
        // This catch is very powerfull, it can catch all errors
        .catch((err: Response) => {
          // The err.statusText is empty if server down (err.type === 3)
          console.log((err.statusText || "Can't join the server."));
          // Really usefull. The app can't catch this in "(err)" closure
          reject((err.statusText || "Can't join the server."));
          // This return is required to compile but unuseable in your app
          return Observable.throw(err);
        })
        // The (err) => {} param on subscribe can't catch server down error so I keep only the catch
        .subscribe(data => { resolve(data) })
    })
  }
}

在应用程序中:
this.itemService.toSaveItem(item).then(
  (res) => { console.log('success', res) },
  (err) => { console.log('error', err) }
)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接