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

引言

基本上當下的應用都會分為前端與后端,當然這種前端定義不在限于桌面瀏覽器仅政、手機、APP等設備。一個良好的后端會通過一套所有前端都通用的 RESTful API 序列接口作為前后端之間的通信蒂誉。

這其中對于身份認證都不可能再依賴傳統(tǒng)的Session或Cookie;轉而使用諸如OAuth2距帅、JWT等這種更適合API接口的認證方式右锨。當然本文并不討論如何去構建它們。

一碌秸、API 設計

首先雖然并不會討論身份認證的技術绍移,但不管是OAuth2還是JWT本質上身份認證都全靠一個 Token 來維持悄窃;因此,下面統(tǒng)一以 token 來表示身份認證所需要的值蹂窖。

一套合理的API規(guī)則轧抗,會讓前端編碼更優(yōu)雅。因此恼策,希望在編寫Angular之前鸦致,能與后端相互達成一種“協(xié)議”也很有必要』量可以嘗試從以下幾點進行考慮分唾。

版本號

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

業(yè)務節(jié)點

以一個節(jié)點來表示某個業(yè)務,比如:

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

動作

由HTTP動詞來表示:

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

統(tǒng)一響應

這一點非常重要碳褒,特別是當我們新建一個商品時,商品的屬性非常多睦授,但如果我們缺少某個屬性時∩径ィ可以使用這樣的一種統(tǒng)一的響應格式:

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

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

狀態(tài)碼

后端響應一個請求是包括狀態(tài)碼和響應內容淑廊,而每一種狀態(tài)碼又包含著不同的含義逗余。

  • 200 成功返回請求數據
  • 401 無權限
  • 404 無效資源

二、如何訪問Http季惩?

首先录粱,需要導入 HttpClientModule 模塊。

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

@NgModule({
    imports: [
        HttpClientModule
    ]
})

然后画拾,在組件類注入 HttpClient关摇。

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

最后,請求點擊某個按鈕發(fā)送一次GET請求碾阁。

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

打印結果:

{{ user | async | json }}

三個簡單的步驟输虱,就是一個完整的HTTP請求步驟。

然后脂凶,現實與實際是有一些距離宪睹,比如說身份認證愁茁、錯誤處理、狀態(tài)碼處理等問題亭病,在上面并無任何體現鹅很。

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

因此……

三、攔截器

1整袁、HttpInterceptor 接口

正如其名菠齿,我們在不改變上面應用層面的代碼下,允許我們把身份認證坐昙、錯誤處理绳匀、狀態(tài)碼處理問題給解決了!

寫一個攔截器也是非常的優(yōu)雅炸客,只需要實現 HttpInterceptor 接口即可疾棵,而且只有一個 intercept 方法。

@Injectable()
export class JWTInterceptor implements HttpInterceptor {

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

}

intercept 方法有兩個參數痹仙,它幾乎所當下流行的中間件概念一般是尔,req 表示當前請求數據(包括:url、參數开仰、header等)嗜历,next 表示調用下一個“中間件”。

2抖所、身份認證

req 有一個 clone 方法,允許對當前的請求參數進行克隆并且這一過程會自行根據一些參數推導痕囱,不管如何用它來產生一個新的請求數據田轧,并在這個新數據中加入我們期望的數據,比如:token鞍恢。

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

當然傻粘,你可以再折騰更多請求前的一些配置。

最后帮掉,把新請求參數傳遞給下一個“中間件”弦悉。

return next.handle(jwtReq);

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

3涩搓、異常處理

仔細再瞧 next.handle 返回的是一個 Observable 類型污秆∨恚看到 Observable 我們會想到什么?flatMap良拼、catch 等一大堆東西战得。

因此,我們可以利用這些操作符來改變響應的值庸推。

flatMap

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

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

只會在請求成功才會返回一個 HttpResponse 類型蚓庭,因此致讥,我們可以大膽判斷是否來源于 HttpResponse 來表示HTTP請求已經成功。

這里器赞,統(tǒng)一對業(yè)務層級的錯誤 code !== 0 產生一個錯誤信號的 Observable垢袱。反之,產生一個成功的信息港柜。

catch

catch 來捕獲非200以外的其他狀態(tài)碼的錯誤请契,比如:401。同時夏醉,前面的 flatMap 所產生的錯誤信號爽锥,也會在這里被捕獲到。

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

4畔柔、完整代碼

至此氯夷,攔截器所要包括的身份認證token、統(tǒ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)
            .flatMap((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:
                        // 權限處理
                        location.href = ''; // 重新登錄
                        break;
                    case 200:
                        // 業(yè)務層級錯誤處理
                        this.notifySrv.error('業(yè)務錯誤', `錯誤代碼為:${res.body.code}`);
                        break;
                    case 404:
                        this.notifySrv.error('404', `API不存在`);
                        break;
                }
                // 以錯誤的形式結束本次請求
                return Observable.throw(res);
            })
    }
}

發(fā)現沒有,我們并沒有加一大堆并不認識的事物玄捕,單純都只是對數據流的各種操作而已踩蔚。

NotifyService 是一個無須依賴HTML模板、極簡Angular通知組件枚粘。

5馅闽、注冊攔截器

攔截器構建后,還需要將其注冊至 HTTP_INTERCEPTORS 標識符中。

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

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

以上是攔截器的所有內容捞蛋,在不改變原有的代碼的情況下孝冒,我們只是利用短短幾行的代碼實現了身份認證所需要的TOKEN、業(yè)務級統(tǒng)一響應處理拟杉、錯誤處理動作庄涡。

四、async 管道

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

{{ user | async | json }}

它相當于:

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

然而,async 這種簡化拿穴,并不代表失去某些自由度泣洞,比如說當在獲取數據過程中顯示【加載中……】,怎么辦默色?

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

恩球凰!

五、結論

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

當我們說到優(yōu)雅使用HTTP這件事時甩挫,易測試是一個非常重要,因此椿每,我建議將HTTP從組件類中剝離并將所有請求放到 Service 當中伊者。當對某個組件編寫測試代碼時,如果受到HTTP請求結果的限制會讓測試更困難间护。

Happy coding!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末亦渗,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子汁尺,更是在濱河造成了極大的恐慌法精,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件均函,死亡現場離奇詭異,居然都是意外死亡菱涤,警方通過查閱死者的電腦和手機苞也,發(fā)現死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘秆,“玉大人如迟,你說我怎么就攤上這事。” “怎么了殷勘?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵此再,是天一觀的道長。 經常有香客問我玲销,道長输拇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任贤斜,我火速辦了婚禮策吠,結果婚禮上,老公的妹妹穿的比我還像新娘瘩绒。我一直安慰自己猴抹,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布锁荔。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪靖避。 梳的紋絲不亂的頭發(fā)上蒸殿,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音嘱丢,去河邊找鬼薪介。 笑死,一個胖子當著我的面吹牛越驻,可吹牛的內容都是我干的汁政。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼缀旁,長吁一口氣:“原來是場噩夢啊……” “哼记劈!你這毒婦竟也來了?” 一聲冷哼從身側響起并巍,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤目木,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懊渡,有當地人在樹林里發(fā)現了一具尸體刽射,經...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年剃执,在試婚紗的時候發(fā)現自己被綠了誓禁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡肾档,死狀恐怖摹恰,靈堂內的尸體忽然破棺而出辫继,到底是詐尸還是另有隱情,我是刑警寧澤俗慈,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布姑宽,位于F島的核電站,受9級特大地震影響闺阱,放射性物質發(fā)生泄漏炮车。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一馏颂、第九天 我趴在偏房一處隱蔽的房頂上張望示血。 院中可真熱鬧,春花似錦救拉、人聲如沸难审。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽告喊。三九已至,卻和暖如春派昧,著一層夾襖步出監(jiān)牢的瞬間黔姜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工蒂萎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秆吵,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓五慈,卻偏偏與公主長得像纳寂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泻拦,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理毙芜,服務發(fā)現,斷路器争拐,智...
    卡卡羅2017閱讀 134,711評論 18 139
  • 一腋粥、概念(載錄于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434閱讀 8,373評論 6 152
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,305評論 25 707
  • 今天想分享一首歌,金莎的《全世界我只貪一個他》 全世界我只貪一個他 他會在哪座城等待我 他有心疼我的靈魂 赤誠的胸...
    雁兒與安閱讀 279評論 0 1
  • 文/小面包 本周天氣:晴轉多云 本周心情:多云轉晴 【2017.10.8】漏油危機 第一眼看到這張圖時展辞,腦子嗡的一...
    面包不打烊閱讀 423評論 28 29