[Hero Of UnderGround 地下城] — 5F AQI 全台空氣指標儀表板

RexHung
11 min readMar 5, 2019

--

成果 & 程式碼

Demo:點我
Code Source:點我

Introduction & 前言

超級菜鳥這次來挑戰第五層的BOSS了,這次必須串接API取的資料後Show在網頁上,對剛接觸JS不久以及根本沒碰過API的我是一大挑戰啊...

Summary & 摘要

由於JS地下城每個BOSS的弱點都不一樣,每一層都要由弱點去進行攻略,本次BOSS的弱點有三項。

第四點是自己加上的(笑

  • 【特定技術】必須使用 AJAX 技術串接資料 API,不可直些寫死資料在變數上。
  • 【特定技術】上方切換城市(高雄、台北)後,下方會切換該城市的各地區。
  • 【解決問題】糟糕,BOSS 使用屏蔽魔法將 API 出處移除了,身為勇者的你必須查出 API 的下落,才能順利擊敗此 BOSS。什麼,你說會有 CORS 問題?嗯… 身為勇者的你,一定可以找各種服務來解決的,畢竟你是「勇者」嘛 (燦笑。
  • 【書寫能力】請寫一篇 BLOG 來介紹你的挑戰過程,你攻略此 BOSS 的攻略過程心得,底層 XMLHttpRequest、Fetch API 的差異,使用 Promise 來優化 XMLHttpRequest JAX,探討 CORS 問題解決方案。

尋找API?

作為第一次尋找API的我很幸運的,收尋了關鍵字就出現了,一切都要感謝谷歌大神(跪

進入後只需要再收尋欄位輸入關鍵字,或者到下面的各種標籤點擊搜尋就能找到了!

關於CORS & XMLHttpRequest & Promise 優化相關

跨來源資源共用(Cross-Origin Resource Sharing (CORS))是一種使用額外 HTTP 標頭令目前瀏覽網站的使用者代理取得存取其他來源(網域)伺服器特定資源權限的機制。當使用者代理請求一個不是目前文件來源 — — 例如來自於不同網域(domain)、通訊協定(protocol)或通訊埠(port)的資源時,會建立一個跨來源 HTTP 請求(cross-origin HTTP request)

這邊引用MDN Web docs的文章,簡單來說,因為安全考量,API端如果沒有開啟CORS的話,我們把做好的網頁發布到公開網站上,資料是沒辦法撈到的。

CORS跨網域問題

處理的方法有幾種,一種是透過後端,利用伺服器端程式來繞過此問題,但是必須自己架設一個伺服器,亦或者使用國外免費架設伺服器,但是這邊採取其他方式。

前人種樹後人乘涼

感謝各種前端大神的幫忙,像是pvt5r486大大文章內就提到使用別人建立好的服務,例如這個:

但是這個方法我在使用上發現速度似乎載入的比較慢,有時還有撈不到資料的問題(或許是我太菜?!

最後我使用了另一種大神推薦的方法,那就是Google App Script!

使用 Google Apps Script 做中繼點取得跨網域API資料

首先必須要有一個google帳號,然後進入雲端硬碟,點擊左上角新增,第一次會看不到 Google App Script 這個程式,必須點擊最下面的連結更多應用程式。

點擊 +連線 新增APP Script

然後再裡面貼上一段程式碼:

function doGet(e){
var param = e.parameter;
var url = param.url;
var response = UrlFetchApp.fetch(decodeURIComponent(url),{
headers: { "Content-type" : "application/json" }
});
var data = JSON.parse(response.getContentText());
return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON);
}

接著點下 發布 → 部署為網絡應用程式

這邊很重要,記得選 任何人,甚至是匿名使用者 不然一樣有跨網域問題

部署方式為:部署的網址?參數名稱= API 網址

這邊這樣說我也是理解了大半天,我資質愚鈍呀…

簡單說 部署的網址? 不用理她 參數名稱就是 url ,你只需要在後面輸入 url=API 網址,之後他會產出一組網址給你 把那組網址跟你的這串 url=API 網址 結合後就可以使用了。

就是這串啦~

留下這串網址之後,以後只要有API的問題,加上他就對啦。

這邊感謝Mandy大神還有prt5r486大神文章

XMLHttpRequest 和 Fetch API 差異

一般Ajax指的就是XMLHttpRequest(XHR),而Fetch API 是基于 Promise 設計的,後面我們會提到 Promise。

有興趣可以點我看文章

XMLHttpRequest 本質上但並不是一個設計優良的 API: + 不符合關注分離(Separation of Concerns)的原則 + 配置和調用方式非常混亂 + 使用事件機制來跟蹤狀態變化 + 基於事件的異步模型沒有現代的 Promise,generator/yield,async/await 友好 。

Fetch API 旨在修正上述缺陷,它提供了與 HTTP 語義相同的 JS 語法,簡單來說,它引入了 fetch() 這個實用的方法來獲取網絡資源。

Promise 優化 XMLHttpRequest JAX

再接觸前端部分之前,有讀過PHP的書,不過裡面也是用到Ajax,在朋友的建議下,想著第一次串接API不如就直接學習AXIOS,因為他本身也有 Promise 功能了。

function getapi() {     let promise = new Promise((resolve, reject) => {     let xhr = new XMLHttpRequest();     xhr.open('get', API, true); //API取好名字後在全域變數宣告後帶入網址     xhr.send(null);     xhr.onload = () => {       if (xhr.status >= 200 && xhr.status < 400) {         resolve(xhr.response);       } else {           reject("取得資料失敗: " + xhr.status);       }     }   });
promise.then((res) => {
data = JSON.parse(res); alldata(); //成功後讓他進入下一個function }); promise.catch((error) => { console.log(error); //api讀取失敗時console.log });}

有關Axios可以上官方GitHub查看
因為以前完全沒接觸過Ajax及Axios,在查看了各位大神的文章後,
Promise的架構大致如下。

let promise = new Promise((resolve, reject) => {
if (...) {
resolve();
} else {
reject();
}
});
promise.then((res) => {

});
promise.catch((error) => {
});

而白話一點就是可以理解成式子的可能為:

承諾 被兌現 (fulfilled)

→用 resolve() 來兌現

承諾 被打破 (rejected)

→用 reject() 來表示失敗

承諾 一直沒有回應 (pending)

→一直沒有回傳

而在這些承諾之後:

承諾被兌現 就 繼續做預定好的下一件事

使用 .then()

承諾被打破 就 根據這個原因去做對應的動作

使用 .catch(),或是 .then 的第二個參數

承諾 一直都沒有回應 就 繼續等下去

resolve() 運行 → .then 承諾被兌現
reject() 運行 → .catch 承諾失敗

然而因為我們使用 XMLHttpRequest() 取得結果會是字串,必須使用JSON.parse() 轉成 JSON 才能使用。

善用 filter() Method 點我前往

參考各位大神文章後,了解了基本方法如下:

let arr = ['apple', 'banana', 'lemon', 'apple', 'watermelon', 'grape'];
let result = arr.filter((item, index, array) =>{
console.log(item, index, array);
return item;
});
  • item — 當前是 arr 陣列中的哪一個值,如「apple」
  • index — 這個值在 arr 陣列中的索引,如「apple」的索引為 0
  • array — 這個陣列的內容
function alldata() {     let result = data.filter((item, index, array) => {        if ($.inArray(item.County, Country) == -1) {           Country.push(item.County); //判斷有無重複並放入縣市名稱            $('.select-box').append('<option>' + item.County + '</option>');            return true;            } else {            return false;            }      });}

在item裡我們已經將所有資料分開了,方便我們過濾資料,而這邊我們使用if條件是判斷不讓相同縣市的資料出現第二次以上,因為這邊要把縣市放到上方的Select內的Option。

不得不說真的是常常用到組字串呢

下方的資料一樣使用 filter() 及 組字串方式,把各縣市的資料放上div,造出一個一個的div(誰叫我不會Vue呢TAT

jQuery.data() 方法點我

這邊值得一提,因為使用重組字串的方法,所以用到了 jQuery 的 data() ,而造出來的字,會被HTML自動翻成小寫,資質愚鈍如我花了大把時間再找為何我的onclick事件抓不到呢…

Conclusion & 結論

這系列的地下城真的幫助我JS成長了許多,每每覺得要卡關了,還是能慢慢爬文慢慢吸收,然後破關,我想最主要的還是邏輯部分,簡化自己的程式碼,另外在這次做API之前我根本毫無頭緒,這是很好的練習,也是我的第一次(羞

最後的最後一樣要加強的也是文筆部分,看各位大神都精簡扼要,我都還是露露長。雖然這只是剛開始寫Blog的第二篇廢文,還是要列出檢討的地方,然後朝下一關繼續加油,完畢。

  • JS
  • 邏輯
  • 文筆
  • 體重

我的其他範例

  • Hero Of UnderGround — 1F Multiplicatio 九九乘法表

Demo:點我
Code Source:點我

  • Hero Of UnderGround — 2F Clock 時鐘

Demo:點我
Code Source:點我

  • Hero Of UnderGround — 3F Calculator 計算機

Demo:點我
Code Source:點我

  • Hero Of UnderGround — 4F World Clock 各國時區

Demo:點我
Code Source:點我

參考網站

pvt5r486大神的Blog — 全台空氣指標攻略心得
Mandy大神的Bolg — 利用google apps script做中繼點跨網域遠端取得api資料henry35208大神的鐵人邦 — Promise介紹
axios的GitHub — axios
camsong大神的GitHubg Bolg — 傳統Ajax已死,Fetch永生
W3school 教學網站
行政院環保署資料開放平臺
巴哈姆特 舒壓用

--

--

RexHung

菜雞學Code之路,期許自己哪天能成長茁壯。