Node.js 原生模块怎么发 HTTP 请求?
Node.js 本身自带 http
和 https
模块,不用额外装依赖,适合做基础请求,先从最常见的 GET、POST 说起。
GET 请求:一行代码发起,重点处理响应
用 http.get()
能快速发 GET 请求,它自动帮你处理请求方法、路径这些细节,比如调个公开 API 拿数据:
const http = require('http'); http.get('http://api.example.com/data', (res) => { let data = ''; // 分段接收响应体(大响应时会分片) res.on('data', (chunk) => { data += chunk; }); // 响应结束后处理数据 res.on('end', () => { try { const result = JSON.parse(data); console.log('拿到数据:', result); } catch (err) { console.error('解析失败:', err); } }); // 监听网络错误(比如连不上服务器) res.on('error', (err) => { console.error('请求出错:', err); }); }).on('error', (err) => { // 请求本身的错误(如DNS解析失败) console.error('发起请求失败:', err); });
这里要注意,响应是“流”形式,得用 data
事件把分片拼起来,end
事件里处理完整数据。
POST 请求:手动构造请求头和请求体
POST 需要自己设置请求方法、请求头(Content-Type
),还要把请求体写进去,举个发 JSON 数据的例子:
const http = require('http'); const postData = JSON.stringify({ name: '小明', age: 18 }); const options = { hostname: 'api.example.com', path: '/user', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': postData.length // 告诉服务端body长度 } }; const req = http.request(options, (res) => { // 同样用data和end事件收响应 let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.log('POST 响应:', data); }); }); req.on('error', (err) => { console.error('POST 出错:', err); }); // 把请求体写进请求 req.write(postData); req.end(); // 一定要end,否则请求发不出去
原生 POST 步骤多些:配 options
、建请求对象、写 body、监听响应和错误,要是发表单(application/x-www-form-urlencoded
),原理类似,只是 postData
改成 name=小明&age=18
这种格式,再调整 Content-Type
就行。
原生模块的优缺点
优点是“零依赖”,项目不想装第三方库时很省心;缺点也明显——代码繁琐(比如处理 JSON 解析、错误分支),复杂场景(如拦截请求、自动重试)得自己写逻辑,所以简单请求用原生,复杂需求更适合第三方库。
常用第三方库有啥优势?怎么选?
Node.js 生态里第三方 HTTP 库一大堆,核心是帮你简化代码、处理重复逻辑,挑几个最火的讲讲:
axios:前后端通吃的“万金油”
前端开发者很熟,Node 里也能装(npm i axios
),它基于 Promise,语法简洁,还支持请求拦截、响应拦截、自动转换 JSON,比如发 GET:
const axios = require('axios'); axios.get('http://api.example.com/data') .then(res => console.log(res.data)) .catch(err => console.error('请求失败:', err));
POST 更简单,直接传数据:
axios.post('http://api.example.com/user', { name: '小明', age: 18 }) .then(res => console.log(res.data)) .catch(err => { /* 处理错误 */ });
优势是“前后端一致”,团队技术栈统一时特方便;拦截器能统一加 token、处理错误(401 自动跳登录),但体积稍大,纯 Node 项目追求极致轻量的话,可能选更小巧的库。
got:轻量又现代的“新生代选手”
安装 npm i got
,API 设计很“Node 风格”,支持流、Promise、TypeScript,还内置重试、超时、cookie 管理,比如发请求并流式处理大文件:
const got = require('got'); // 把响应直接存到本地文件(流处理,不占内存) got.stream('https://example.com/big-file.zip').pipe(fs.createWriteStream('file.zip'));
普通 JSON 请求更简洁:
(async () => { const res = await got.get('http://api.example.com/data', { responseType: 'json' }); console.log(res.body); // 自动解析成对象 })();
适合 Node 后端项目,尤其是要处理大文件、需要高可配置性的场景,文档也写得特清楚。
superagent:链式调用写着爽
安装 npm i superagent
,语法像写句子一样流畅:
const superagent = require('superagent'); superagent .get('http://api.example.com/data') .query({ page: 1, size: 10 }) // 拼查询参数 .set('Authorization', 'Bearer xxx') // 设请求头 .end((err, res) => { // 回调风格,也支持Promise if (err) return console.error(err); console.log(res.body); });
链式调用对新手友好,一眼能看懂每步做啥,处理文件上传更方便:
superagent .post('http://api.example.com/upload') .attach('file', './avatar.jpg') // 自动转multipart/form-data .end((err, res) => { /* ... */ });
适合快速写 Demo 或小项目,代码可读性拉满。
node-fetch:前端开发者无缝迁移
如果你习惯浏览器里的 fetch
,装 npm i node-fetch
就能在 Node 里用同款 API:
const fetch = require('node-fetch'); fetch('http://api.example.com/data') .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error(err));
优点是学习成本低,前端同学能快速上手;但功能相对基础,复杂需求(如重试)得自己加逻辑或装插件。
选库思路:
简单请求用 axios
或 superagent
写着快;处理大文件、需要高级配置选 got
;想和前端代码复用选 axios
或 node-fetch
,要是项目极轻量,原生模块也够。
不同场景下怎么选工具?
光知道库还不够,得看场景挑方案,举几个典型场景:
批量请求+并发控制
比如要调 100 个接口,全并发容易把服务器打崩,得限制同时请求数,用 axios
+ Promise.all
+ 分组:
const axios = require('axios'); const urls = ['url1', 'url2', ..., 'url100']; // 100个地址 const batchSize = 10; // 每次发10个 const results = []; async function batchRequest() { for (let i = 0; i < urls.length; i += batchSize) { const batch = urls.slice(i, i + batchSize); const requests = batch.map(url => axios.get(url)); const resList = await Promise.all(requests); results.push(...resList.map(r => r.data)); } console.log('所有结果:', results); } batchRequest();
要是用 got
,可以用 got.pipeline
做流处理,更高效处理大量请求。
文件上传(multipart/form-data)
原生模块得自己拼请求头和边界,特麻烦,用 superagent
的 .attach()
一步到位:
const superagent = require('superagent'); superagent .post('https://api.example.com/upload') .attach('avatar', './avatar.jpg') // 文件名和路径 .attach('doc', './report.pdf') // 多文件上传 .end((err, res) => { if (err) return console.error(err); console.log('上传结果:', res.body); });
axios
也能做,不过得装 form-data
库配合:
const axios = require('axios'); const FormData = require('form-data'); const form = new FormData(); form.append('avatar', fs.createReadStream('./avatar.jpg')); axios.post('https://api.example.com/upload', form, { headers: form.getHeaders() // 自动设Content-Type和边界 }).then(res => console.log(res.data));
处理大响应(避免内存爆炸)
比如下载大文件,用原生 http
的流管道:
const http = require('http'); const fs = require('fs'); const file = fs.createWriteStream('large-file.zip'); http.get('http://example.com/large-file.zip', (res) => { res.pipe(file); // 响应流直接写到文件,不用存内存 file.on('finish', () => { file.close(); console.log('下载完成'); }); }).on('error', (err) => { fs.unlink('large-file.zip'); // 下载失败删临时文件 console.error('下载出错:', err); });
用 got
更简单,开流选项就行:
const got = require('got'); got.stream('http://example.com/large-file.zip').pipe(fs.createWriteStream('file.zip'));
自动重试+超时控制
网络不稳定时,请求失败要自动重试。got
内置重试逻辑:
const got = require('got'); got.get('http://api.example.com/data', { retry: { limit: 3 }, // 失败重试3次 timeout: { request: 5000 } // 5秒没响应算超时 }).then(res => console.log(res.body)) .catch(err => console.error('最终失败:', err));
axios
得装插件(如 axios-retry
):
const axios = require('axios'); const axiosRetry = require('axios-retry'); axiosRetry(axios, { retries: 3 }); // 全局生效 axios.get('http://api.example.com/data', { timeout: 5000 }) .then(res => console.log(res.data)) .catch(err => console.error('重试后失败:', err));
异步处理和错误排查有啥技巧?
Node.js 里发请求几乎都是异步的,处理不好容易掉坑,这部分讲怎么理顺异步逻辑,快速定位错误。
用 Promise/async/await 管理异步
原生模块默认是回调风格,改成 Promise 更顺手,比如把 http.get
包成 Promise:
function getPromise(url) { return new Promise((resolve, reject) => { http.get(url, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => resolve(data)); res.on('error', (err) => reject(err)); }).on('error', (err) => reject(err)); }); } // 用async/await调用 (async () => { try { const data = await getPromise('http://api.example.com/data'); console.log(data); } catch (err) { console.error('请求失败:', err); } })();
第三方库基本都支持 Promise,直接用 async/await
写同步风格代码,逻辑更清晰。
错误分类处理
请求可能遇到三类错误:
网络层错误:DNS 解析失败、连不上服务器(原生里是
req.on('error')
,第三方库用catch
捕获)。响应层错误:服务器返回 4xx(如参数错)、5xx(服务端崩了),原生要自己查
res.statusCode
,第三方库(如axios
)会把状态码非 2xx 的情况抛到catch
。业务层错误:比如接口返回
{ code: 1001, msg: 'token过期' }
,得自己解析响应体判断。
举个 axios
处理所有错误的例子:
axios.get('http://api.example.com/data') .then(res => { if (res.data.code !== 0) { // 业务错误 return Promise.reject(new Error(res.data.msg)); } return res.data; }) .catch(err => { if (err.response) { // 响应了但状态码不对(4xx/5xx) console.error('服务端错误:', err.response.status, err.response.data); } else if (err.request) { // 发了请求但没响应(超时、断网) console.error('网络错误:', err.request); } else { // 其他错误(如参数错) console.error('请求前错误:', err.message); } });
超时设置的坑
原生模块默认没超时,得自己加 setTimeout
+ abort
:
const req = http.get(url, (res) => { /* 处理响应 */ }); const timeout = setTimeout(() => { req.abort(); // 超时后终止请求 console.error('请求超时'); }, 5000); req.on('end', () => clearTimeout(timeout)); // 成功后清定时器
第三方库一般有 timeout
选项,直接配数字就行,axios
的 { timeout: 5000 }
。
实战案例:从接口请求到数据处理
光说不练假把式,用几个真实场景练手。
案例1:调用天气API(原生 vs axios)
假设用“和风天气”API,先拿原生 http
实现:
const http = require('http'); const apiKey = '你的密钥'; const city = '北京'; const url = `https://devapi.qweather.com/v7/weather/now?location=${city}&key=${apiKey}`; http.get(url, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { const weather = JSON.parse(data); console.log(`当前${city}天气:${weather.now.text},温度${weather.now.temp}°C`); }); }).on('error', (err) => { console.error('请求失败:', err); });
换成 axios
后,代码更简洁:
const axios = require('axios'); const apiKey = '你的密钥'; const city = '北京'; async function getWeather() { try { const res = await axios.get('https://devapi.qweather.com/v7/weather/now', { params: { location: city, key: apiKey } }); const { text, temp } = res.data.now; console.log(`当前${city}天气:${text},温度${temp}°C`); } catch (err) { console.error('查天气失败:', err); } } getWeather();
案例2:分页接口循环请求
比如某接口返回列表,支持 page
参数,要把所有页数据捞回来,用 got
做并发控制:
const got = require('got'); const baseUrl = 'https://api.example.com/list'; const pageSize = 20; let currentPage = 1; const allData = []; async function fetchPage() { try { const res = await got.get(baseUrl, { searchParams: { page: currentPage, size: pageSize }, responseType: 'json' }); allData.push(...res.body.items); if (res.body.total > currentPage * pageSize) { // 还有下一页 currentPage++; await fetchPage(); // 递归调自己 } else { console.log('所有数据:', allData); } } catch (err) { console.error('分页请求失败:', err); } } fetchPage()
网友评论文明上网理性发言 已有0人参与
发表评论: