Fetch API與POST請求參數(shù)格式那些事

一文讀懂Fetch API與POST請求的四種參數(shù)格式

簡述

相信不少前端開發(fā)童鞋與后端聯(lián)調(diào)接口時(shí),都會(huì)碰到前端明明已經(jīng)傳了參數(shù),后端童鞋卻說沒有收到,尤其是post請求,遇到的非常多。本文以node.js作為服務(wù)端語言,借用express框架,簡要分析客戶端發(fā)送post請求的四種方式以及服務(wù)端如何接收。本文客戶端請求沒有借助第三方ajax庫,采用的是Fetch API,雖然瀏覽器兼容性有點(diǎn)問題,但是用法簡潔靈活,以后可能會(huì)是一個(gè)趨勢。在說post請求之前,先簡要概述下Fetch API。

Fetch API

Fetch API提供了一個(gè)獲取資源的接口(包括跨域請求),提供了更強(qiáng)大和靈活的功能集。未來可能是XMLHttpRequest的一種替代方案。去年GitHub代碼去jQuery重構(gòu)時(shí),就使用Fetch API替代jQueryajax,畢竟目前JavaScript很多原生語法都進(jìn)行了大量精簡,比如DOM操作API、http請求fetch、es6+等。今天的axios可能就是明日的jQuery!

簡單的實(shí)例

Fetch API主要暴露了三個(gè)接口一個(gè)方法。


  • 三個(gè)接口
    • Request(資源請求)
    • Response(請求的響應(yīng))
    • Headers(Request/Response頭部信息)
  • 一個(gè)方法
    • fetch()(獲取資源調(diào)用的方法)
// 實(shí)例化一個(gè)Request實(shí)例
// 第一個(gè)參數(shù)一般指資源路徑
// 第二個(gè)參數(shù)可以理解為請求的配置項(xiàng),包含頭部信息和http請求一些關(guān)鍵配置(請求類型、參數(shù)...)
let requestInstance = new Request('/hello', {
    method: 'post',
    headers: {
        'Content-Type': 'application/json;charset=utf-8'
    },
    body: '{"hello": "world"}'
})
// fetch方法參數(shù)同Request實(shí)例
// 第一個(gè)參數(shù)為url或者Request實(shí)例
// 第二個(gè)參數(shù)為請求配置項(xiàng)
fetch(requestInstance).then(response => {
    // 返回的是一個(gè)Response的實(shí)例
    // 調(diào)用Response實(shí)例的序列化方法,序列化成json,返回值是一個(gè)promise
    // 序列化方法有 json,text,formData,blob,arrayBuffer,redirct
    let result = response.json()
    result.then(res => {
        consolee.log(res)
    })
})

有意思的特性

Fetch API添加了一個(gè)實(shí)驗(yàn)性的功能,支持客戶端手動(dòng)取消http請求了,這個(gè)比較有意思,因?yàn)橹暗?code>ajax貌似都不支持手動(dòng)取消。借助AbortSignal接口,可以通過AbortController實(shí)例化一個(gè)控制器,將實(shí)例的siginal當(dāng)做請求的配置項(xiàng),傳遞到服務(wù)端,客戶端可以通過AbortController實(shí)例的abort方法,來終止當(dāng)前的http請求,示例代碼如下:

<template>
    <button id='btn'>中止請求</button>
</template>
<script>
    // 實(shí)例化controller
    var controller = new AbortController()
    // 獲取實(shí)例的signal接口
    var signal = controller.signal
    let btn = document.getElementById('btn')
    // 點(diǎn)擊按鈕,中止請求
    btn.addEventListener('click', e => {
        controller.abort()
    })
    // json方式提交數(shù)據(jù)
    const url = 'http://192.168.43.216:3000'
    // 將signal接口放到請求配置項(xiàng)中
    let testRequest = new Request(url + '/test', {
        method: 'post',
        headers: {
            'Content-Type': 'application/json;charset=utf-8;'
        },
        body: '{"foo":"bar"}',
        signal
    })
    fetch(testRequest).then(response => {
        let result = response.text()
        result.then(res => {
            console.log(res)
        })
    })
</script>

post請求四種傳參方式

本文所說的前端傳遞數(shù)據(jù)格式相對于主流的ajax函數(shù)庫有一定區(qū)別,一般的ajax函數(shù)庫為了方便用戶使用,都會(huì)對數(shù)據(jù)進(jìn)行二次封裝。本文主要說原始的數(shù)據(jù)格式交互,具體ajax庫的使用,還是以官方文檔為準(zhǔn)。
請求頭(Request Headers)的實(shí)體Content-Type用于指示資源的MIME類型,即客戶端傳遞消息的格式;響應(yīng)頭中Content-Type用于指示服務(wù)端返回消息的格式。所以在http請求中,我們可以從報(bào)文中的Content-Type屬性來判斷客戶端-服務(wù)端消息傳遞的格式。

JSON提交

JSON是常用的一種前后端數(shù)據(jù)接收格式。前端傳遞的是鍵值對數(shù)據(jù),即對象(Object)。采用JSON傳遞參數(shù),請求頭Content-Typeapplication/json;charset=utf-8,其中charset為采用的字符集。

注意點(diǎn):

  1. 既然為JSON提交,就要對參數(shù)進(jìn)行序列化,即JSON.stringify(params),否則傳遞到服務(wù)端的參數(shù)可能是[Object object]
  2. 服務(wù)端(node.js)是以流的方式進(jìn)行接收,接收完是一個(gè)JSON字符串,調(diào)用JSON.parse(params)可以對參數(shù)進(jìn)行序列化

示例代碼


客戶端:

const url = 'http://192.168.43.216:3000'
let testRequest = new Request(url + '/test', {
    method: 'post',
    headers: {
        'Content-Type': 'application/json;charset=utf-8;'
    },
    body: JSON.stringify({a: 1})
})
fetch(testRequest).then(response => {
    let result = response.text()
    result.then(res => {
        console.log(res)
    })
})

服務(wù)端:

router.post('/test', (req, res, next) => {
    let data = ''
    req.on('data', chunk => {
        data += chunk
    })
    req.on('end', () => {
        // 將JSON字符串解析成對象
        data = JSON.parse(data)
        res.send(data)
    })
})

請求頭提交

在實(shí)際開發(fā)中,遇到過不少后端開發(fā),喜歡吧請求參數(shù)放在請求頭,類似于get請求,即請求的參數(shù)是拼接在請求地址后面。個(gè)人覺得這種傳參方式并不好,一般瀏覽器對URL長度是有限制的,以Chrome為例,URL最大長度正在7700個(gè)字符左右,對于post請求來說,最好參數(shù)還是放在body中。

注意點(diǎn)

  1. 客戶端請求參數(shù)拼接在url后,在?后,鍵值對寫法a=1,多個(gè)鍵值對之間通過連接符&連接
  2. 服務(wù)端能夠在request對象中,通過request.query直接進(jìn)行接收
  3. 由于參數(shù)是拼接在url后面,所以請求頭Content-Type無需設(shè)置

示例代碼


客戶端:

let queryStringRequest = new Request(`${url}/querystring?a=1&b=2`, {
    method: 'post'
})
fetch(queryStringRequest).then(response => {
    let result = response.json()
    result.then(res => {
        console.log(res)
    })
})

服務(wù)端:

router.post('/querystring', (req, res, next) => {
    res.send(req.query)
})

普通表單提交

表單提交的方式有兩種,一種是普通的表單提交,另外一種是通過FormData進(jìn)行提交(主要應(yīng)用在文件上傳)。單純的表單提交,與上述兩種參數(shù)格式上還是存在一定的差別的,主要體現(xiàn)在以下幾個(gè)方面。

  1. Content-Type
    表單提交Request HeadersContent-Typeapplication/x-www-form-urlencoded;charset=utf-8。
  2. 參數(shù)
    表單提交參數(shù)是放在body中,感覺是JSON和請求頭提交的合體。參數(shù)位置與JSON提交相同,參數(shù)格式與請求頭提交一致

示例代碼


客戶端:

 let formRequest = new Request(url + '/form', {
    method: 'post',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
    },
    body: 'a=1&b=2'
})
fetch(formRequest).then(response => {
    let result = response.json()
    result.then(res => {
        console.log(res)
    })
})

服務(wù)端:

const fs = require('fs')
router.post('/form', (req, res, next) => {
    let data = ''
    req.on('data', chunk => {
        data += chunk
    })

    req.on('end', () => {
        data = decodeURI(data)
        // 將a=1&b=2解析成{a: 1, b: 2}
        let dataObj = querystring.parse(data)
        res.send(dataObj)
    })
})

FormData提交 (文件上傳)

通常我們在進(jìn)行文件上傳時(shí),都會(huì)采用表單提交。參數(shù)放在body中,只不過格式與普通的有差別,具體如下:

  1. 參數(shù)需要放在FormData的實(shí)例中,通過append進(jìn)行參數(shù)的添加
  2. 請求頭Content-Typemultipart/formdata

示例代碼


客戶端:

<template>
    <input type="file" id="uploadFile">
</template>
<script>
let $input = document.getElementById('uploadFile')
// 監(jiān)聽文件上傳
$input.addEventListener('change', e => {
    let file = e.target.files[0]
    handleUploadFile(file)
})

function handleUploadFile (file) {
    let bean = new FormData()
    bean.append('file', file)
    bean.append('hello', 'world')
    let uploadFileRequest = new Request(`${url}/upload`, {
        method: 'post',
        headers: {
            'Content-Type': 'multipart/formdata'
        },
        body: bean
    })
    fetch(uploadFileRequest).then(response => {
        let result = response.text()
        result.then(res => {
            console.log(res)
        })
    })
}
</script>

服務(wù)端:

router.post('/upload', (req, res, next) => {
    let data = []
    let size = 0
    req.on('data', chunk => {
        data.push(chunk)
        size += chunk.length
    })
    let rems = []
    req.on('end', () => {
        let buffer = Buffer.concat(data, size)
        for (let i = 0; i < buffer.length; i++) {
            var v = buffer[i];
            var v2 = buffer[i+1];
            if(v==13 && v2==10){
                rems.push(i);
            }
        }
        // 圖片信息
        var picmsg_1 = buffer.slice(rems[0]+2,rems[1]).toString();
        var filename = picmsg_1.match(/filename=".*"/g)[0].split('"')[1];

        // 圖片數(shù)據(jù)
        var nbuf = buffer.slice(rems[3]+2,rems[rems.length-2]);
        var path = './static/'+filename;
        fs.writeFileSync(path , nbuf);
        res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8'});
        res.end('<div id="path">'+path+'</div>');
    })
})

小結(jié)

post請求向服務(wù)端提交參數(shù),一般情況下都是放在body中,但是從上文列舉的幾種傳參方式,仍然可以放在請求頭中傳遞,服務(wù)端對于在請求頭中傳遞的參數(shù)的處理和get請求保持一致。此外,從node.js接收的參數(shù)來看,除了放在請求頭中能夠直接獲取外,其余三種請求方式都是以字節(jié)流的方式傳遞到服務(wù)端的。熟悉post請求的幾種傳參方式,有助于我們和后端同學(xué)進(jìn)行接口聯(lián)調(diào)。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容