介紹一個請求庫 — Undici

語言: CN / TW / HK

theme: juejin highlight: atom-one-dark


前言

在瀏覽器中,如果想發起一個請求,我們以前會使用到 xhr,不過這種底層 api,往往呼叫方式比較簡陋。為了提高開發效率, jQuery 的 $.ajax 可能是最好的選擇,好在後來出現了更加現代化的 fetch api 。

但是考慮到 fetch 的相容性,而且它也不支援一些全域性性的配置,以及請求中斷,在實際的使用過程中,我們可能會用到 axios 請求庫,來進行一些請求。到了 Node.js 中,幾乎都會通過 request 這個庫,來進行請求。遺憾的是,request 在兩年前就停止維護了,在 Node.js 中需要找到一個能夠替代的庫還挺不容易的。

在 request 的 issues 中,有一個表格推薦了一些在 Node.js 中常用的請求庫:

| 包名 | 包大小 | API風格 | 簡介 | | ------------------------------------------------------ | ------ | ------------------ | ------------------------------------------------------------ | | node-fetch | 0.4kb | promise / stream | A light-weight module that brings window.fetch to Node.js | | got | 48.4kb | promise / stream | Simplified HTTP requests | | axios | 11.9kb | promise / stream | Promise based HTTP client for the browser and node.js | | superagent | 18kb | chaining / promise | Small progressive client-side HTTP request library, and Node.js module with the same API, sporting many high-level HTTP client features | | urllib | 816kb | callback / promise | Help in opening URLs (mostly HTTP) in a complex world — basic and digest authentication, redirections, cookies and more. |

瀏覽器中使用比較多的 axios,在 Node.js 中並不好用,特別是要進行檔案上傳的時候,會有很多意想不到的問題。

最近我在網上🏄🏿的時候,發現 Node.js 官方是有一個請求庫的:undici,名字取得還挺複雜的。所以,今天的文章就來介紹一下 undici。順便提一句,undici 是義大利語 11 的意思,好像雙十一也快到了,利好茅臺🤔。

Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici. It is also a Stranger Things reference.

上手

我們可以直接通過 npm 來安裝 undici

bash npm install undici -S

undici 對外暴露一個物件,該物件下面提供了幾個 API:

  • undici.fetch:發起一個請求,和瀏覽器中的 fetch 方法一致;
  • undici.request:發起一個請求,和 request 庫有點類似,該方法支援 Promise;
  • undici.stream:處理檔案流,可以用來進行檔案的下載;

undici.fetch

注意:該方法需要 node 版本 >= v16.5.0

在通過 undici.fetch 請求服務之前,需要先通過 koa 啟動一個簡單登入服務。

```js const Koa = require('koa') const bodyParser = require('koa-bodyparser')

const app = new Koa()

app.use(bodyParser()) app.use(ctx => { const { url, method, body } = ctx.request if (url === '/login') { if (method === 'POST') { if (body.account === 'shenfq' && body.password === '123456') { ctx.body = JSON.stringify({ name: 'shenfq', mobile: '130xxxxxx' }) return } } } ctx.status = 404 ctx.body = JSON.stringify({}) })

app.listen(3100) ```

上面程式碼很簡單,只支援接受一個 POST 方法到 /login 路由。下面使用 undici.fetch 發起一個 POST 請求。

```js const { fetch } = require('undici')

const bootstrap = async () => { const api = 'http://localhost:3100/login' const rsp = await fetch(api, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ account: 'shenfq', password: '123456' }) }) if (rsp.status !== 200) { console.log(rsp.status, '請求失敗') return } const json = await rsp.json() console.log(rsp.status, json) }

bootstrap() ```

如果將請求的方式改為 GET,就會返回 404。

js const rsp = await fetch(api, { method: 'GET' })

undici.request

undici.request 的呼叫方式與 undici.fetch 類似,傳參形式也差不多。

```js const { request } = require('undici')

const bootstrap = async () => { const api = 'http://localhost:3100/login' const { body, statusCode } = await request(api, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ account: 'shenfq', password: '123456' }) }) const json = await body.json() console.log(statusCode, json) }

bootstrap() ```

只是返回結果有點不一樣,request 方法返回的 http 響應結果在 body 屬性中,而且該屬性也支援同 fetch 類似的 .json()/.text() 等方法。

中斷請求

安裝 abort-controller 庫,然後例項化 abort-controller,將中斷訊號傳入 request 配置中。

npm i abort-controller

```js const undici = require('undici') const AbortController = require('abort-controller')

// 例項化 abort-controller const abortController = new AbortController() undici.request('http://127.0.0.1:3100', { method: 'GET', // 傳入中斷訊號量 signal: abortController.signal, }).then(({ statusCode, body }) => { body.on('data', (data) => { console.log(statusCode, data.toString()) }) }) ```

我們執行程式碼,發現是可以請求成功的,是因為我們沒有主動呼叫中斷方法。

```js undici.request('http://127.0.0.1:3100', { method: 'GET', signal: abortController.signal, }).then(({ statusCode, body }) => { console.log('請求成功') body.on('data', (data) => { console.log(statusCode, data.toString()) }) }).catch(error => { // 捕獲由於中斷觸發的錯誤 console.log('error', error.name) })

// 呼叫中斷 abortController.abort() ```

現在執行程式碼會發現,並沒有輸出 請求成功 的日誌,進入了 catch 邏輯,成功的進行了請求的中斷。

undici.steam

undici.steam 方法可以用來進行檔案下載,或者介面代理。

檔案下載

js const fs = require('fs')const { stream } = require('undici')const out = fs.createWriteStream('./宋代-哥窯-金絲鐵線.jpg')const url = 'http://img.dpm.org.cn/Uploads/Picture/dc/cegift/cegift6389.jpg'stream(url, { opaque: out }, ({ opaque }) => opaque)

介面代理

js const http = require('http')const undici = require('undici')// 將 3100 埠的請求,代理到 80 埠const client = new undici.Client('http://localhost')http.createServer((req, res) => { const { url, method } = req client.stream( { method, path: url,opaque: res }, ({ opaque }) => opaque )}).listen(3100)

總結

本文只是介紹了 undici 幾個 api 的使用方式,看起來 undici 上手難道還是比較低的。但是相容性還不太行,比如,fetch 只支援 [email protected] 以上的版本。

對於這種比較新的庫,個人還是建議多觀望一段時間,雖然 request 已經廢棄了,我們還是使用一些經過較長時間考驗過的庫,比如,egg 框架中使用的 urllib,還有一個 node-fetch,上手難道也比較低,與瀏覽器中的 fetch api 使用方式一致。