當我開始(和我的團隊)用TypeScript 和Svelte(我們都討厭的JavaScript 和React)重寫我們的應用程式時,我遇到了一個問題:
如何安全地輸入 HTTP 回應的所有可能的正文?
這對你來說有啟發嗎?如果沒有,你很可能就是“其中之一”,呵呵。讓我們暫時離題一下,以便更好地理解圖片。
為什麼這個領域似乎未經探索
似乎沒有人關心 HTTP 回應的“所有可能的主體”,因為我找不到為此做的任何東西(好吧,也許是 ts-fetch)。讓我快速透過我的邏輯來解釋為什麼會這樣。
沒有人關心,因為人們:
只關心happy path:HTTP狀態碼為2xx時的反應體。
人們在其他地方手動輸入它。
對於#1,我想說的是,開發人員(尤其是沒有經驗的開發人員)忘記了 HTTP 請求可能會失敗,失敗回應中攜帶的資訊很可能與常規回應完全不同。
對於#2,讓我們深入研究 ky 和axios 等流行 NPM 套件中發現的一個大問題。
資料獲取包的問題
據我所知,人們喜歡像 ky 或 axios 這樣的包,因為它們的「功能」之一是它們會在不正常的 HTTP 狀態碼上拋出錯誤。從什麼時候開始可以這樣了?自從從來沒有。但顯然人們並沒有意識到這一點。人們很高興並且滿足於在不正常的回應中遇到錯誤。
我想人們在捕捉的時候會輸入不正常的身體。多麼混亂,多麼有代碼味道!
這是一種程式碼味道,因為您有效地使用 try..catch 區塊作為分支語句,而 try..catch 並不意味著是分支語句。
但即使你與我爭論分支在 try..catch 中自然發生,還有另一個導致這種情況仍然不好的重要原因:當拋出錯誤時,運行時需要展開調用堆疊。就 CPU 週期而言,這比使用 if 或 switch 語句的常規分支要昂貴得多。
知道了這一點,你能證明僅僅因為濫用 try..catch 區塊而造成的效能損失是合理的嗎?我說不。我想不出有什麼理由能讓前端世界對此感到非常滿意。
既然我已經解釋了我的推理,那麼讓我們回到正題吧。
問題,詳細
HTTP 回應可能會根據其狀態代碼攜帶不同的資訊。例如,接收 PATCH HTTP 請求的 todo 端點(例如 api/todos/:id)在回應狀態碼為 200 時可能會傳回具有不同正文的回應,而回應狀態碼為 400 時可能會傳回不同正文的回應。
舉例:
// For the 200 response, a copy of the updated object: { "id": 123, "text": "The updated text" } // For the 400 response, a list of validation errors: { "errors": [ "The updated text exceeds the maximum allowed number of characters." ] }
因此,考慮到這一點,我們回到問題陳述:如何鍵入執行此 PATCH 請求的函數,其中 TypeScript 可以告訴我正在處理哪個主體,具體取決於我編寫的 HTTP 狀態碼程式碼?答:使用串流語法(建構器語法、鍊式語法)來累積類型。
建構解決方案
讓我們先定義一個基於先前類型建構的類型:
export type AccumType<t newt> = T | NewT; </t>
超級簡單:給定類型 T 和 NewT,將它們連接起來形成一個新類型。在 AccumType 中再次使用這個新類型作為 T,然後就可以累積另一個新類型。然而,這手工完成的並不好。讓我們介紹一下解決方案的另一個關鍵部分:流暢的語法。
流暢的語法
給定類別 X 的一個對象,其方法始終返回其自身(或自身的副本),可以將方法呼叫一個接一個地連結起來。這是流暢的語法,或是鍊式語法。
讓我們寫一個簡單的類別來執行此操作:
export class NamePending<t> { accumulate<newt>() { return this as NamePending<accumtype newt>>; } } // Now you can use it like this: const x = new NamePending(); // x is of type NamePending. const y = x.accumulate // y is of type NamePending. </accumtype></newt></t>
尤里卡!我們已經成功地將流暢的語法和我們編寫的類型結合起來,開始將資料類型累積為單一類型!
如果不明顯,您可以繼續練習,直到累積了所需的類型(x.accumulate().accumulate()…直到完成)。
這一切都很好,但是這個超級簡單的類型並沒有將 HTTP 狀態碼綁定到相應的正文類型。
完善我們所擁有的
我們想要的是為 TypeScript 提供足夠的信息,以便其類型縮小功能發揮作用。為此,我們需要執行必要的操作來取得與原始問題相關的程式碼(在每個檔案中鍵入 HTTP 回應的正文) -狀態碼基礎)。
首先,重新命名並進化 AccumType。下面的程式碼顯示了迭代的進展:
// Iteration 1. export type FetchResult<t newt> = T | NewT; // Iteration 2. export type FetchResponse<tstatus extends number tbody> = { ok: boolean; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends number newt> = T | FetchResponse<tstatus newt>; //Makes sense to rename NewT to TBody. </tstatus></t></tstatus></t>
此時,我意識到:狀態代碼是有限的:我可以(並且確實)查找它們並為它們定義類型,並使用這些類型來限制類型參數 TStatus:
// Iteration 3. export type OkStatusCode = 200 | 201 | 202 | ...; export type ClientErrorStatusCode = 400 | 401 | 403 | ...; export type ServerErrorStatusCode = 500 | 501 | 502 | ...; export type StatusCode = OkStatusCode | ClientErrorStatusCode | ServerErrorStatusCode; export type NonOkStatusCode = Exclude<statuscode okstatuscode>; export type FetchResponse<tstatus extends statuscode tbody> = { ok: TStatus extends OkStatusCode ? true : false; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends statuscode tbody> = T | FetchResponse<tstatus tbody>; </tstatus></t></tstatus></statuscode>
我們已經得到了一系列非常漂亮的類型:透過基於 ok 或 status 屬性上的條件進行分支(編寫 if 語句),TypeScript 的類型縮小功能將啟動!不信,我們寫一下class部分來試試:
export class DrFetch<t> { for<tstatus extends statuscode tbody>() { return this as DrFetch<fetchresult tstatus tbody>>; } } </fetchresult></tstatus></t>
試駕一下:
// For the 200 response, a copy of the updated object: { "id": 123, "text": "The updated text" } // For the 400 response, a list of validation errors: { "errors": [ "The updated text exceeds the maximum allowed number of characters." ] }
現在應該清楚為什麼類型縮小能夠根據 status 屬性的 ok 屬性正確預測分支時主體的形狀。
但是,有一個問題:實例化類別時的初始類型,在上面的註解區塊中標記。我是這樣解決的:
export type AccumType<t newt> = T | NewT; </t>
這個小改變有效地排除了最初的輸入,我們現在開始營業了!
現在我們可以寫如下程式碼,Intellisense 將 100% 準確:
export class NamePending<t> { accumulate<newt>() { return this as NamePending<accumtype newt>>; } } // Now you can use it like this: const x = new NamePending(); // x is of type NamePending. const y = x.accumulate // y is of type NamePending. </accumtype></newt></t>
在查詢 ok 屬性時,類型縮小也會起作用。
如果您沒有註意到,我們可以透過不拋出錯誤來編寫更好的程式碼。根據我的專業經驗,axios 是錯的,ky 是錯的,其他 fetch helper 做同樣的事情都是錯的。
結論
TypeScript 確實很有趣。透過結合 TypeScript 和 Fluent 語法,我們能夠根據需要累積盡可能多的類型,這樣我們就可以從第一天開始編寫更準確、更清晰的程式碼,而不是一遍又一遍地調試。這項技術已被證明是成功的,並且可供任何人嘗試。安裝 dr-fetch 並測試它:
// Iteration 1. export type FetchResult<t newt> = T | NewT; // Iteration 2. export type FetchResponse<tstatus extends number tbody> = { ok: boolean; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends number newt> = T | FetchResponse<tstatus newt>; //Makes sense to rename NewT to TBody. </tstatus></t></tstatus></t>
更複雜的包
我還創建了 wj-config,一個旨在完全消除過時的 .env 檔案和 dotenv 的軟體包。該套件還使用此處教授的 TypeScript 技巧,但它使用 & 而不是 | 連接類型。如果您想嘗試一下,請安裝 v3.0.0-beta.1。不過,打字要複雜得多。在 wj-config 之後進行 dr-fetch 簡直就是在公園散步。
有趣的東西:那裡有什麼
讓我們看看與 fetch 相關的套件中的一些錯誤。
同構獲取
您可以在自述文件中看到:
// Iteration 3. export type OkStatusCode = 200 | 201 | 202 | ...; export type ClientErrorStatusCode = 400 | 401 | 403 | ...; export type ServerErrorStatusCode = 500 | 501 | 502 | ...; export type StatusCode = OkStatusCode | ClientErrorStatusCode | ServerErrorStatusCode; export type NonOkStatusCode = Exclude<statuscode okstatuscode>; export type FetchResponse<tstatus extends statuscode tbody> = { ok: TStatus extends OkStatusCode ? true : false; status: TStatus; statusText: string; body: TBody }; export type FetchResult<t tstatus extends statuscode tbody> = T | FetchResponse<tstatus tbody>; </tstatus></t></tstatus></statuscode>
「伺服器回應錯誤」? ?沒有。 「伺服器說你的請求不好」。是的,投擲部分本身就很糟糕。
ts 獲取
這個想法是正確的,但不幸的是只能輸入 OK 與非 OK 回應(最多 2 種類型)。
凱
我最批評的軟體包之一,展示了這個例子:
export class DrFetch<t> { for<tstatus extends statuscode tbody>() { return this as DrFetch<fetchresult tstatus tbody>>; } } </fetchresult></tstatus></t>
這是一個非常初級的開發人員會寫的:只是快樂的道路。根據其自述文件,等效性:
const x = new DrFetch(); // Ok, having to write an empty type is inconvenient. const y = x .for() .for() ; /* y's type: DrFetch */
投擲部分太糟糕了:為什麼要分支到投擲,強迫你稍後接住?這對我來說毫無意義。錯誤中的文字也具有誤導性:它不是「獲取錯誤」。抓取成功了。你得到了回應,不是嗎?你只是不喜歡它……因為這不是快樂的道路。更好的措詞是「HTTP 請求失敗:」。失敗的是請求本身,而不是取得操作。
以上是如何使用 TypeScript 累積類型:輸入所有可能的 fetch() 結果的詳細內容。更多資訊請關注PHP中文網其他相關文章!

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

記事本++7.3.1
好用且免費的程式碼編輯器

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

WebStorm Mac版
好用的JavaScript開發工具