詳解如何在Angular優(yōu)雅編寫HTTP請求

引言

創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比騰沖網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式騰沖網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋騰沖地區(qū)。費(fèi)用合理售后完善,十多年實(shí)體公司更值得信賴。

基本上當(dāng)下的應(yīng)用都會分為前端與后端,當(dāng)然這種前端定義不在限于桌面瀏覽器、手機(jī)、APP等設(shè)備。一個(gè)良好的后端會通過一套所有前端都通用的 RESTful API 序列接口作為前后端之間的通信。

這其中對于身份認(rèn)證都不可能再依賴傳統(tǒng)的Session或Cookie;轉(zhuǎn)而使用諸如OAuth3、JWT等這種更適合API接口的認(rèn)證方式。當(dāng)然本文并不討論如何去構(gòu)建它們。

一、API 設(shè)計(jì)

首先雖然并不會討論身份認(rèn)證的技術(shù),但不管是OAuth3還是JWT本質(zhì)上身份認(rèn)證都全靠一個(gè) Token 來維持;因此,下面統(tǒng)一以 token 來表示身份認(rèn)證所需要的值。

一套合理的API規(guī)則,會讓前端編碼更優(yōu)雅。因此,希望在編寫Angular之前,能與后端相互達(dá)成一種“協(xié)議”也很有必要??梢試L試從以下幾點(diǎn)進(jìn)行考慮。

版本號

可以在URL(例:https://demo.com/v1/)或Header(例:headers: { version: 'v1' } )中體現(xiàn),相比較我更喜歡前者的直接。

業(yè)務(wù)節(jié)點(diǎn)

以一個(gè)節(jié)點(diǎn)來表示某個(gè)業(yè)務(wù),比如:

  • 商品 https://demo.com/v1/product/
  • 商品SKU https://demo.com/v1/product/sku/

動作

由HTTP動詞來表示:

  • GET 請求一個(gè)商品 /product/${ID}
  • POST 新建一個(gè)商品 /product
  • PUT 修改一個(gè)商品 /product/${ID}
  • DELETE 刪除一個(gè)商品 /product/${ID}

統(tǒng)一響應(yīng)

這一點(diǎn)非常重要,特別是當(dāng)我們新建一個(gè)商品時(shí),商品的屬性非常多,但如果我們?nèi)鄙倌硞€(gè)屬性時(shí)??梢允褂眠@樣的一種統(tǒng)一的響應(yīng)格式:

{
  "code": 100, // 0 表示成功
  "errors": { // 錯(cuò)誤明細(xì)
    "title": "商品名稱必填"
  }
}

其中 code 不管成功與否都會有該屬性。

狀態(tài)碼

后端響應(yīng)一個(gè)請求是包括狀態(tài)碼和響應(yīng)內(nèi)容,而每一種狀態(tài)碼又包含著不同的含義。

  • 200 成功返回請求數(shù)據(jù)
  • 401 無權(quán)限
  • 404 無效資源

二、如何訪問Http?

首先,需要導(dǎo)入 HttpClientModule 模塊。

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule
  ]
})

然后,在組件類注入 HttpClient。

export class IndexComponent {
  constructor(private http: HttpClient) { }
}

最后,請求點(diǎn)擊某個(gè)按鈕發(fā)送一次GET請求。

user: Observable<User>;
getUser() {
  this.user = this.http.get<User>('/assets/data/user.json');
}

打印結(jié)果:

{{ user | async | json }}

三個(gè)簡單的步驟,就是一個(gè)完整的HTTP請求步驟。

然后,現(xiàn)實(shí)與實(shí)際是有一些距離,比如說身份認(rèn)證、錯(cuò)誤處理、狀態(tài)碼處理等問題,在上面并無任何體現(xiàn)。

可,上面已經(jīng)足夠優(yōu)雅,要讓我破壞這種優(yōu)雅那么此文就變得無意義了!

因此……

三、攔截器

1、HttpInterceptor 接口

正如其名,我們在不改變上面應(yīng)用層面的代碼下,允許我們把身份認(rèn)證、錯(cuò)誤處理、狀態(tài)碼處理問題給解決了!

寫一個(gè)攔截器也是非常的優(yōu)雅,只需要實(shí)現(xiàn) HttpInterceptor 接口即可,而且只有一個(gè) intercept 方法。

@Injectable()
export class JWTInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
    // doing
  }

}

intercept 方法有兩個(gè)參數(shù),它幾乎所當(dāng)下流行的中間件概念一般,req 表示當(dāng)前請求數(shù)據(jù)(包括:url、參數(shù)、header等),next 表示調(diào)用下一個(gè)“中間件”。

2、身份認(rèn)證

req 有一個(gè) clone 方法,允許對當(dāng)前的請求參數(shù)進(jìn)行克隆并且這一過程會自行根據(jù)一些參數(shù)推導(dǎo),不管如何用它來產(chǎn)生一個(gè)新的請求數(shù)據(jù),并在這個(gè)新數(shù)據(jù)中加入我們期望的數(shù)據(jù),比如:token。

const jwtReq = req.clone({
  headers: req.headers.set('token', 'xxxxxxxxxxxxxxxxxxxxx')
});

當(dāng)然,你可以再折騰更多請求前的一些配置。

最后,把新請求參數(shù)傳遞給下一個(gè)“中間件”。

return next.handle(jwtReq);

等等,都 return 了,說好的狀態(tài)碼、異常處理呢?

3、異常處理

仔細(xì)再瞧 next.handle 返回的是一個(gè) Observable 類型。看到 Observable 我們會想到什么?mergeMap、catch 等一大堆東西。

因此,我們可以利用這些操作符來改變響應(yīng)的值。

mergeMap

請求過程中會會有一些過程狀態(tài),比如請求前、上傳進(jìn)度條、請求結(jié)束等,Angular在每一次這類動作中都會觸次 next。因此,我們只需要在返回 Observable 對象加上 mergeMap 來觀察這些值的變更,這樣有非常大的自由空間想象。

return next.handle(jwtReq).mergeMap((event: any) => {
    if (event instanceof HttpResponse && event.body.code !== 0) {
      return Observable.create(observer => observer.error(event));
    }
    return Observable.create(observer => observer.next(event));
  })

只會在請求成功才會返回一個(gè) HttpResponse 類型,因此,我們可以大膽判斷是否來源于 HttpResponse 來表示HTTP請求已經(jīng)成功。

這里,統(tǒng)一對業(yè)務(wù)層級的錯(cuò)誤 code !== 0 產(chǎn)生一個(gè)錯(cuò)誤信號的 Observable。反之,產(chǎn)生一個(gè)成功的信息。

catch

catch 來捕獲非200以外的其他狀態(tài)碼的錯(cuò)誤,比如:401。同時(shí),前面的 mergeMap 所產(chǎn)生的錯(cuò)誤信號,也會在這里被捕獲到。

.catch((res: HttpResponse<any>) => {
  switch (res.status) {
    case 401:
      // 權(quán)限處理
      location.href = ''; // 重新登錄
      break;
    case 200:
      // 業(yè)務(wù)層級錯(cuò)誤處理
      alert('業(yè)務(wù)錯(cuò)誤:' + res.body.code);
      break;
    case 404:
      alert('API不存在');
      break;
  }
  return Observable.throw(res);
})

4、完整代碼

至此,攔截器所要包括的身份認(rèn)證token、統(tǒng)一響應(yīng)處理、異常處理都解決了。

@Injectable()
export class JWTInterceptor implements HttpInterceptor {

  constructor(private notifySrv: NotifyService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
    console.log('interceptor')
    const jwtReq = req.clone({
      headers: req.headers.set('token', 'asdf')
    });
    return next
      .handle(jwtReq)
      .mergeMap((event: any) => {
        if (event instanceof HttpResponse && event.body.code !== 0) {
          return Observable.create(observer => observer.error(event));
        }
        return Observable.create(observer => observer.next(event));
      })
      .catch((res: HttpResponse<any>) => {
        switch (res.status) {
          case 401:
            // 權(quán)限處理
            location.href = ''; // 重新登錄
            break;
          case 200:
            // 業(yè)務(wù)層級錯(cuò)誤處理
            this.notifySrv.error('業(yè)務(wù)錯(cuò)誤', `錯(cuò)誤代碼為:${res.body.code}`);
            break;
          case 404:
            this.notifySrv.error('404', `API不存在`);
            break;
        }
        // 以錯(cuò)誤的形式結(jié)束本次請求
        return Observable.throw(res);
      })
  }
}

發(fā)現(xiàn)沒有,我們并沒有加一大堆并不認(rèn)識的事物,單純都只是對數(shù)據(jù)流的各種操作而已。

NotifyService 是一個(gè)無須依賴HTML模板、極簡Angular通知組件。

5、注冊攔截器

攔截器構(gòu)建后,還需要將其注冊至 HTTP_INTERCEPTORS 標(biāo)識符中。

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true}
  ]
})

以上是攔截器的所有內(nèi)容,在不改變原有的代碼的情況下,我們只是利用短短幾行的代碼實(shí)現(xiàn)了身份認(rèn)證所需要的TOKEN、業(yè)務(wù)級統(tǒng)一響應(yīng)處理、錯(cuò)誤處理動作。

四、async 管道

一個(gè) Observable 必須被訂閱以后才會真正的開始動作,前面在HTML模板中我們利用了 async 管道簡化了這種訂閱過程。

{{ user | async | json }}

它相當(dāng)于:

let user: User;
get() {
  this.http.get<User>('/assets/data/user.json').subscribe(res => {
    this.user = res;
  });
}
{{ user | json }}

然而,async 這種簡化,并不代表失去某些自由度,比如說當(dāng)在獲取數(shù)據(jù)過程中顯示【加載中……】,怎么辦?

<div *ngIf="user | async as user; else loading">
  {{ user | json }}
</div>
<ng-template #loading>加載中……</ng-template>

恩!

五、結(jié)論

Angular在HTTP請求過程中使用 Observable 異步數(shù)據(jù)流控制數(shù)據(jù),而利用 rxjs 提供的大量操作符,來改變最終值;從而獲得在應(yīng)用層面最優(yōu)雅的編碼風(fēng)格。

當(dāng)我們說到優(yōu)雅使用HTTP這件事時(shí),易測試是一個(gè)非常重要,因此,我建議將HTTP從組件類中剝離并將所有請求放到 Service 當(dāng)中。當(dāng)對某個(gè)組件編寫測試代碼時(shí),如果受到HTTP請求結(jié)果的限制會讓測試更困難。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

分享文章:詳解如何在Angular優(yōu)雅編寫HTTP請求
URL地址:http://muchs.cn/article14/jejgde.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)微信公眾號、、企業(yè)網(wǎng)站制作、用戶體驗(yàn)網(wǎng)站策劃

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)