使用 POST 請求


HTTP 定義 POST 來發送資料給伺服器,POST 適用於非等冪操作,若請求有副作用,多次 POST 請求的結果可以不同,它並非安全操作,可以用在修改資料庫的內容,或在伺服器上儲存檔案等。

如果要發送 POST,則可以在非同步物件 open 時,將第一個參數設為 'POST',在這「之後」使用 setRequestHeader 設定內容類型,這是因為 POST 要發送的資料會放在請求的本體中,必須告知發送的資料類型為何,接著在 send 時,將要發送的資料,作為 send 的引數傳入。

例如,若發送表單類型資料,必須設置請求標頭 'Content-Type''application/x-www-form-urlencoded',以下是個示範:

...
let url = 'somewhere';
let queryString = 'a=10&b=20';
xmlHttp.open('POST', url);
xmlHttp.setRequestHeader('Content-Type',  'application/x-www-form-urlencoded');
xmlHttp.send(queryString); 

放在 POST 本體中的資料,也有可能是其他格式,例如 XML 或 JSON,你必須設定不同的請求標頭,這在之後還會說明。

在下面這個例子中,將先前對 XMLHttpRequest 的基本操作進行了簡單的封裝,並使用 POST 來實作〈使用 GET 請求〉 中第二個範例:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    新增書籤:<br>
    網址:<input id="url" type="text">
    <span id="message" style="color:red"></span><br>
    名稱:<input type="text">

<script type="text/javascript">    

    // 組合與編碼請求參數
    function params(paraObj) {
        return Object.keys(paraObj)
                     .map(name => {
                         let paraName = encodeURIComponent(name);
                         let paraValue = encodeURIComponent(paraObj[name]);                         
                         return `${paraName}=${paraValue}`.replace(/%20/g, '+');
                     })
                     .join('&');
    }

    // 對 XMLHttpRequest 做簡單封裝
    class XHR {
        constructor() {
            let xhr = new XMLHttpRequest();

            // 你還可以加入其他處理器的設置…
            // 像是 loadstart、progress、abort、error、timeout、loadend 等
            let handlers = {
                'readystatechange' : new Set(),
                'load' : new Set()
            };

            xhr.onreadystatechange = function(evt) {
                handlers['readystatechange']
                    .forEach(handler => handler.call(xhr, evt));
            };

            xhr.onload = function(evt) {
                handlers['load']
                    .forEach(handler => handler.call(xhr, evt));
            };

            this.xhr = xhr;
            this.handlers = handlers;
        }

        addEvt(evtType, handler) {
            this.handlers[evtType].add(handler);
            return this;
        }

        removeEvt(evtType, handler) {
            this.handlers[evtType].delete(handler);
            return this;
        }

        open(method, url, paraObj, async = true, username = null, password = null) {
            let openUrl = paraObj ? `${url}?${params(paraObj)}` : url; 
            this.xhr.open(method, openUrl, async, username, password);
            return this;
        }

        addHeaders(headers) {
            Object.keys(headers)
                  .forEach(name => this.xhr.setRequestHeader(name, headers[name]));
            return this;
        }

        send(body = null) {
            this.xhr.send(body);
            return this;
        }
    }

    document.getElementById('url').onblur = function() {
        let reqString = params({ 
            url : document.getElementById('url').value 
        });

        let xhr = new XHR();
        xhr.addEvt('load', evt => {
            let req = evt.target;
            if(req.status === 200 && req.responseText === 'existed') {
                document.getElementById('message').innerHTML = 'URL 已存在';
            }            
        })
        .open('POST', 'POST-1.php')
        .addHeaders({'Content-Type' : 'application/x-www-form-urlencoded'})
        .send(reqString);
    };

</script>

</body>
</html>

按我觀看結果

在事件的處理上,設計了 addEvt 可以加入處理器,以便與先前文件中看到的事件處理封裝具有相同的風格。