簡介 Fetch API


XMLHttpRequest 使用上不便,就算是標準化後的 XMLHttpRequest Level 1 也只是功能上的加強,開發者通常會進一步地使用程式庫封裝,例如〈封裝 Ajax 操作〉做的那些事情。

2014 年 HTML5 正式標準化,Fetch API 是 HTML5 的一部份,Google、Mozilla 在 2015 年於瀏覽器開始提供實作。

從設計的角度來看,Fetch API 就像是集合了過去 Ajax 使用上一些好實踐的集合體,實現了職責分離,Fetch 的工廠函式 fetch 可接受選項物件,而傳回值是個 Promise

直接來看看〈建立 XMLHttpRequest 物件〉中取得表格的範例,若改用 Fetch 的話會如何簡化:

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

    <button id='req'>取得表格</button>
    <div id="table"></div>

<script type="text/javascript">

    document.getElementById('req').onclick = function() {
        fetch('XMLHttpRequest-1.txt')
            .then(resp => resp.text())
            .then(text => document.getElementById('table').innerHTML = text);
    };

</script>   

</body>
</html>

按我觀看執行結果

fetch 會傳回 Promise,結果會是個 Response 實例,可以透過 text 方法取得承諾結果為回應文字的 Promisefetch 預設使用 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('&');
    }

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

        fetch('POST-1.php', {
            method : 'POST',
            headers : {
                'Content-Type' : 'application/x-www-form-urlencoded'
            },
            body : reqString
        })
        .then(resp => resp.text())
        .then(function(text) {
            if(text === 'existed') {
                document.getElementById('message').innerHTML = 'URL 已存在';
            }           
        });
    };

</script>

</body>
</html>

按我觀看執行結果

body 特性也支援 FormData,例如將〈結合 FormData 上傳檔案〉中的範例,改使用 Fetch:

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

    <form id="f" action="upload" method="post" enctype="multipart/form-data">
          Photo  :<input type="file" name="photo"/><br>
        <input id="upload" type="submit"/>
    </form> 

    <span id="message"></span>

<script type="text/javascript">

    document.getElementById('upload').onclick = function(evt) {
        let formData = new FormData(document.getElementById('f'));

        fetch('upload', {
            method : 'POST',
            body : formData
        })
        .then(function(resp) {
            if(resp.status === 200) {
                document.getElementById('message').innerHTML = 'File Uploaded';
            }  
        });

        evt.preventDefault();
    };

</script>

  </body>
</html>

Fetch 也支援跨站請求,可透過初始物件的 mode 特性來設定 'cors'(預設值)、'no-cors'(會發出跨域請求,然而無法讀取回應)、'same-origin'(不發出跨域請求) 等值,例如〈跨站請求 CORS〉中的範例,可以如下改用 Fetch:

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

    <body>
        ID:<input id="id">
        <button id="test">JSONP 測試</button>
        <span id="result"></span>
    </body>

<script type="text/javascript">

    document.getElementById('test').onclick = function() {
        let id = document.getElementById('id').value;
        let result = document.getElementById('result');

        fetch(`https://openhome.cc/Gossip/ECMAScript/samples/CORS-1.php?id=${id}`, {
            mode : 'cors' // 'cors' 是預設值
        })
        .then(resp => resp.json())
        .then(person =>  result.innerHTML = `${person.name}, ${person.age}`);
    };

</script>    

</body>
</html>

按我觀看執行結果

其他初始可以設定的特性,可以參考〈WindowOrWorkerGlobalScope.fetch〉文件的說明。

可以看到的是,對於簡單的請求,使用 Fetch 的方便性,遠大於 XMLHttpRequest