使用 fetch 來進行 Ajax 呼叫

02 November 2015 — Written by Sky Chang
#JavaScript

前言

在很久很久很久很久以前 ( 好啦,其實也沒多久... ),那時 Ajax 正慢慢的紅起來,而如果大家剛好有經過那個時期,大概就會知道 XMLHttpRequest 這個東西,而如果那個時代又真的下去寫後,就會發現,阿,真是 OOXX 的有夠難寫.... 不過,大家也知道,這錯不是錯在 XMLHttpRequest 上,而是時代的混亂阿!! ( 其實現在也沒好到哪去...)

而隨著時代的進步,我們開始擁有了 jQuery ,jQuery 幫我們把 XMLHttpRequest 封裝的好好的,並且解決了各種不同瀏覽器的判斷問題,讓我們快速的進入了新的網頁時代!!那時候看到 jQuery ,真的覺得,阿,他是神物阿!!!

後來,又出現了 KO ( ASP.NET MVC 4 的時代 ),也依舊覺得他是神物....然後直到 Google 家的 NG ( AngularJS ),更又覺得他是神物中的神物,而 AngularJS 更是把一切要用的東西全部都封裝起來了,我們不用再使用 jQuery ( 雖然 NG 裡面也是包 jQuery Lite ),到這個階段時期,我們使用 AngularJS 後,我們幾乎就可以不用管最底層的 XMLHttpRequest ,也不用管在往上的 jQuery 。

但又隨著時代的進步.... ( 其實說真的,也才幾年的時間... ) Facebook 的 React 大舉入侵,但 React 基本是指是代表著 View ,也就是說,他並沒有把一堆東西包的好好的,所以在早期的時候,要進行 Ajax ,可能還是要使用 jQuery ....

但,歷史已經經過了那麼久,我們還是必須要使用 XMLHttpRequest ,其實不然,現在技術大混戰的時期,怎麼可能不推出新的東西哩 ( 爆 ) ,所以這新的東西是甚麼... 就是未來會建立於各個瀏覽器內,新的呼叫方式 fetch .

fetch

那甚麼是 fetch 勒? 為什麼要有這東西,基本上這篇不會談到這個,有興趣的可參考小弟的參考資料,神人大大們寫的已經很詳細了。總之就是要取代掉 XMLHttpRequest 的東西。

那大家可能又會有疑問 " Cool ,但現在所有瀏覽器都支援嗎? " ,這個答案就如同 ES6 一樣,當然不支援阿 ( 狂暴 ) ,但雖然不支援,還是有很多神人寫出了 polyfill ,所以大家要知道的有兩件事情。

  1. fetch 是未來的一個呼叫標準。
  2. 使用這個標準寫出來的 JS Lib 稱為 fetch.js ,來相容不同的瀏覽器. ( 當然,除了 fetch.js 外,也有各神人寫出來不同的 Lib )。

如何使用

這才是這篇的重點,基本上也是小弟自己希望未來要 copy 比較方便,所以順便寫了這篇 XDDD 當然,要完整,還是建議看官網吧,小弟只是貼上自己常用的而已喔~~

Get

我們先用 Get 稍微看一下,基本上 fetch 他使用了 ES6 的 Promise ,所以後面都用 then 來接後續的動作。

所以第一個 then,response 那邊,我們可以先用 status 來判斷 http 給我們的 status code,如果是 200 內,我們才會在使用 response.json() 來將資料轉成 json 格式。

第二個 then 裡面的 data 才是真正的 json 物件.如果沒透過 then 基本上也只是個 Promise 物件而已喔~~

第三個是 catch ,可以捕捉 throw 的例外,而這個例外可以用 new Error 來產生。

最後,同樣的 error 裡面的資料,也必須使用response.json() 轉成物件,也同樣的,要最後一個 then 才能取得。

 fetch('目標URL',{
            method: 'GET',
        }).then(function(response) {
            if (response.status >= 200 && response.status < 300) {
                return response.json()
            } else {
                var error = new Error(response.statusText)
                error.response = response
                throw error
            }
        })
        .then(function(data) {
           // data 才是實際的 JSON 資料
        }).catch(function(error) {
            return error.response.json();
        }).then(function(errorData){
            // errorData 裡面才是實際的 JSON 資料
        });

總之,他回傳的都是 Promise 物件就是了~~

Post

如果是 Post,然後又是要用 JSON 溝通,我們可以加上 headers 這個標頭,而傳送的內容,則是放到 body 這個屬性裡面。

 fetch('網址',{
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        }).then(function checkStatus(response) {
            if (response.status >= 200 && response.status < 300) {
                return response.json();
            } else {
                var error = new Error(response.statusText)
                error.response = response;
                throw error;
            }
        })
        .then(function(data) {
        	//完成
        }).catch(function(error) {
            console.log('request failed', error);
            return error.response.json();
        }).then(function(errorData){
           //失敗
        });

Token 驗證

這邊是自己在後端實作了 OAuth 的驗證,基本上要透過 POST 來傳送 x-www-form-urlencoded 格式的帳號密碼。 所以前面先進行了轉換並塞入到 formData 。

let formData = Object.keys(userForm).map(function (keyName) {
                return encodeURIComponent(keyName) + '=' + encodeURIComponent(userForm[keyName])
                }).join('&');

 fetch('網址',{
            method: 'POST',
            headers: {
                "Content-type": "application/x-www-form-urlencoded"
            },
            body: formData
        }).then(function checkStatus(response) {
            if (response.status >= 200 && response.status < 300) {
                return response.json()
            } else {
                var error = new Error(response.statusText)
                error.response = response
                throw error
            }
        })
        .then(function(data) {
           //成功取得 Token
         }).catch(function(error) {
            console.log('request failed', error);
            return error.response.json();
        }).then(function(errorData){
           //失敗
        });

加上 Token 和 Get

這邊基本上就是加上了 Authorization 的 Header ,比較特別的是後面的 Bearer ${token},這是 ES6 串字串的方法。 ( token 是變數 )。

 fetch('網址',{
            method: 'GET',
             headers: {
                'Authorization': `Bearer ${token}`
            },
        }).then(function checkStatus(response) {
            if (response.status >= 200 && response.status < 300) {
                return response.json()
            } else {
                var error = new Error(response.statusText)
                error.response = response
                throw error
            }
        })
        .then(function(data) {
           //成功取得 Token
         }).catch(function(error) {
            console.log('request failed', error);
            return error.response.json();
        }).then(function(errorData){
           //失敗
        });

搭配 Promise

如果要搭配 Promise,可以這樣寫,底下的範例是,當我們有動態的參數,且每次都必須確保所以請求都完成後,才要繼續作業,這個情況下,我們就可以使用 Promise.all 來進行處理。

    let promiseArray = [];
           
    //用map 和 promiseArray 來收集 pormise , 有時因為網址會需要帶參數,
    //而參數的內容在 datas 裡面,這邊 datas 裡面存放了 ID                              
    datas.map( (id) => {
        promiseArray.push( 
        fetch(`網址\id`,{
            method: 'GET',
            headers: {
                //這邊需要驗證,所以要加上這行,沒有驗證的朋友就不用加了
                'Authorization': `Bearer ${token}`
            },
        }));
        
    });
    
    //使用 Promise.all ,來讓所有請求完成,才繼續下一步
    Promise.all(promiseArray) // 帶入 剛剛 map 回來的 promise Array
    .then( (response) => { 
         // 回來的也是個陣列,而且陣列裡面的物件是 response
         // 因為我們還要確保回傳的 Json 也是要全部處理完,才能繼續...
         // 所以這邊再繼續用 Promise.all 來處理
         // ( Promise.all 裡面帶的參數就是 promise Array
         // 而我們用 response.map 回來的就是 promise 陣列)
         Promise.all(response.map( (res) => { 
              if (res.status >= 200 && res.status < 300) {
                    return res.json()
                } 
            }) //map end
         ) //promise all end
         // 因為我是使用 Promise.all ,
         // 所以這邊的 then 就是 全部傳完成後,傳回來的資料
         .then( (data) => {
            //這邊就看要怎樣處理資料了~~
            //底下是用 forEach 來逐一列出資料
            data.forEach( (d,i) => { 
                console.log(`data is : $(d) ; Index is ${i}`);   
                });    
         })   
    })

後記

基本上這篇就是稍微筆記筆記一下,相信未來應該會有人將這些東西包得更容易使用~~ 同樣的,這個小弟也還在測試中,如有錯誤,有請多包涵喔!!~

參考資料

Sky & Study4.TW