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

成果 & 程式碼

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

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

1
2
3
4
5
6
7
8
9
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 功能了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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) {
esolve(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的架構大致如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
let promise = new Promise((resolve, reject) => {
if (...) {
resolve();
} else {
reject();
}
});
promise.then((res) => {
console.log(res);
});
promise.catch((error) => {
console.log(error);
});

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

承諾 被兌現 (fulfilled)
→用 resolve() 來兌現
承諾 被打破 (rejected)
→用 reject() 來表示失敗
承諾 一直沒有回應 (pending)
→一直沒有回傳

而在這些承諾之後:

承諾被兌現 就 繼續做預定好的下一件事
使用 .then()
承諾被打破 就 根據這個原因去做對應的動作
使用 .catch(),或是 .then 的第二個參數
承諾 一直都沒有回應 就 繼續等下去

resolve() 運行 → .then 承諾被兌現

reject() 運行 → .catch 承諾失敗

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


善用 filter() Method 點我前往

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

1
2
3
4
5
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() 方法點我

這邊值得一提,因為使用重組字串的方法,所以用到了 jQuerydata() ,而造出來的字,會被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:點我

參考網站