第三方 API

我們到目前為止介紹的 API 都內建在瀏覽器中,但並非所有 API 都是如此。許多大型網站和服務,如 Google Maps、Twitter、Facebook、PayPal 等,都提供 API,允許開發者利用其資料(例如,在你的部落格上顯示你的 Twitter 流)或服務(例如,使用 Facebook 登入來登入你的使用者)。本文探討了瀏覽器 API 和第三方 API 之間的區別,並展示了一些後者的典型用法。

先決條件 JavaScript 基礎知識(參見 第一步構建模組JavaScript 物件),以及 客戶端 API 基礎知識
目標 學習第三方 API 的工作原理,以及如何使用它們來增強你的網站。

什麼是第三方 API?

第三方 API 是由第三方(通常是 Facebook、Twitter 或 Google 等公司)提供的 API,允許你透過 JavaScript 訪問其功能並在你的網站上使用它。最明顯的例子之一是使用地圖 API 在你的頁面上顯示自定義地圖。

讓我們看一個 簡單的 Mapquest API 示例,並用它來說明第三方 API 與瀏覽器 API 的區別。

注意:你可能只想 一次獲取我們所有的程式碼示例,在這種情況下,你只需在倉庫中搜索每個部分所需的示例檔案即可。

它們位於第三方伺服器上

瀏覽器 API 內置於瀏覽器中——你可以立即從 JavaScript 訪問它們。例如,我們在 入門文章中看到的 Web Audio API 是使用原生的 AudioContext 物件訪問的。例如

js
const audioCtx = new AudioContext();
// …
const audioElement = document.querySelector("audio");
// …
const audioSource = audioCtx.createMediaElementSource(audioElement);
// etc.

另一方面,第三方 API 位於第三方伺服器上。要從 JavaScript 訪問它們,你首先需要連線到 API 功能並使其在你的頁面上可用。這通常涉及首先透過 <script> 元素連結到伺服器上提供的 JavaScript 庫,如我們的 Mapquest 示例所示

html
<script
  src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"
  defer></script>
<link
  rel="stylesheet"
  href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css" />

然後,你可以開始使用該庫中提供的物件。例如

js
const map = L.mapquest.map("map", {
  center: [53.480759, -2.242631],
  layers: L.mapquest.tileLayer("map"),
  zoom: 12,
});

這裡我們建立一個變數來儲存地圖資訊,然後使用 mapquest.map() 方法建立一個新地圖,該方法的引數是你想要顯示地圖的 <div> 元素的 ID('map'),以及一個包含我們想要顯示的特定地圖詳細資訊的選項物件。在本例中,我們指定了地圖中心的座標、要顯示的型別為 map 的地圖圖層(使用 mapquest.tileLayer() 方法建立),以及預設縮放級別。

這就是 Mapquest API 繪製簡單地圖所需的所有資訊。你連線到的伺服器處理所有複雜的事情,例如顯示正在顯示區域的正確地圖瓦片等。

注意:一些 API 對其功能的訪問方式略有不同,要求開發者向特定 URL 模式發出 HTTP 請求以檢索資料。這些稱為 RESTful API——我們稍後將展示一個示例

它們通常需要 API 金鑰

瀏覽器 API 的安全性傾向於透過許可權提示來處理,如 我們在第一篇文章中所討論的。這樣做的目的是讓使用者知道他們訪問的網站發生了什麼,並且不太可能成為某人以惡意方式使用 API 的受害者。

第三方 API 具有略微不同的許可權系統——它們傾向於使用開發者金鑰來允許開發者訪問 API 功能,這更多是為了保護 API 供應商而不是使用者。

你將在 Mapquest API 示例中找到類似以下內容的行

js
L.mapquest.key = "YOUR-API-KEY-HERE";

此行指定在你的應用程式中使用的 API 或開發者金鑰——應用程式的開發者必須申請獲取金鑰,然後將其包含在他們的程式碼中才能獲得對 API 功能的訪問許可權。在我們的示例中,我們只提供了一個佔位符。

注意:建立你自己的示例時,你將在任何佔位符的位置使用你自己的 API 金鑰。

其他 API 可能要求你以稍微不同的方式包含金鑰,但對於大多數 API 來說,模式都比較相似。

要求金鑰使 API 提供商能夠追究 API 使用者對其行為的責任。當開發者註冊了金鑰後,API 提供商就會知道他們,如果他們開始對 API 做任何惡意的事情(例如跟蹤人們的位置或嘗試用大量請求轟炸 API 以使其停止工作),就可以採取行動。最簡單的行動就是撤銷他們的 API 許可權。

擴充套件 Mapquest 示例

讓我們為 Mapquest 示例新增更多功能,以展示如何使用 API 的其他一些功能。

  1. 要開始本節,請在新的目錄中複製 mapquest 初始檔案。如果你已經 克隆了示例儲存庫,那麼你已經擁有該檔案的副本,你可以在 javascript/apis/third-party-apis/mapquest/start 目錄中找到它。
  2. 接下來,你需要訪問 Mapquest 開發者網站,建立一個帳戶,然後建立一個開發者金鑰以用於你的示例。(在撰寫本文時,它在網站上被稱為“消費者金鑰”,金鑰建立過程還要求提供可選的“回撥 URL”。你無需在此處填寫 URL:只需將其留空即可。)
  3. 開啟你的起始檔案,並將 API 金鑰佔位符替換為你的金鑰。

更改地圖型別

可以使用 Mapquest API 顯示多種不同型別的地圖。為此,請找到以下行

js
layers: L.mapquest.tileLayer("map");

嘗試將 'map' 更改為 'hybrid' 以顯示混合樣式地圖。也嘗試一些其他值。 tileLayer 參考頁面 顯示了不同的可用選項,以及更多資訊。

新增不同的控制元件

地圖提供了一些不同的控制元件;預設情況下,它只顯示縮放控制元件。你可以使用 map.addControl() 方法擴充套件可用的控制元件;將其新增到你的程式碼中

js
map.addControl(L.mapquest.control());

mapquest.control() 方法 只建立一個簡單的功能齊全的控制元件集,預設情況下它位於右上角。你可以透過指定一個包含 position 屬性的選項物件作為控制元件的引數來調整位置,該屬性的值是一個字串,指定控制元件的位置。例如,試試這個

js
map.addControl(L.mapquest.control({ position: "bottomright" }));

還有其他型別的控制元件可用,例如 mapquest.searchControl()mapquest.satelliteControl(),其中一些非常複雜且功能強大。嘗試一下,看看你能想出什麼。

新增自定義標記

在地圖上的某個點新增標記(圖示)很容易——你只需使用 L.marker() 方法(似乎在相關的 Leaflet.js 文件中進行了說明)。將以下程式碼新增到你的示例中,同樣在 window.onload 內部

js
L.marker([53.480759, -2.242631], {
  icon: L.mapquest.icons.marker({
    primaryColor: "#22407F",
    secondaryColor: "#3B5998",
    shadow: true,
    size: "md",
    symbol: "A",
  }),
})
  .bindPopup("This is Manchester!")
  .addTo(map);

如你所見,這在最簡單的情況下需要兩個引數,一個包含要顯示標記的座標的陣列,以及一個包含定義該點要顯示的圖示的 icon 屬性的選項物件。

圖示是使用 mapquest.icons.marker() 方法定義的,如你所見,它包含標記的顏色和大小等資訊。

在第一個方法呼叫的末尾,我們連結 .bindPopup('This is Manchester!'),它定義了單擊標記時要顯示的內容。

最後,我們在鏈的末尾連結 .addTo(map) 以將標記實際新增到地圖中。

嘗試文件中顯示的其他選項,看看你能想出什麼!Mapquest 提供了一些非常高階的功能,例如路線、搜尋等。

注意:如果示例無法正常工作,請將你的程式碼與我們的 完成版本 進行比較。

RESTful API — NYTimes

現在讓我們看另一個 API 示例——紐約時報 API。此 API 允許你檢索紐約時報新聞報道資訊並在你的網站上顯示。這種型別的 API 被稱為 **RESTful API**——我們不像使用 Mapquest 時那樣使用 JavaScript 庫的功能來獲取資料,而是透過向特定 URL 發出 HTTP 請求來獲取資料,並將搜尋詞和其他屬性編碼在 URL 中(通常作為 URL 引數)。這是你在使用 API 時會遇到的常見模式。

下面我們將帶你完成一個練習,向你展示如何使用 NYTimes API,它還提供了一套更通用的步驟,你可以將其用作處理新 API 的方法。

查詢文件

當你想使用第三方 API 時,必須找出文件在哪裡,以便你可以瞭解 API 具有哪些功能,如何使用它們等。紐約時報 API 文件位於 https://developer.nytimes.com/

獲取開發者金鑰

出於安全和問責制的原因,大多數 API 都要求你使用某種開發者金鑰。要註冊 NYTimes API 金鑰,請按照 https://developer.nytimes.com/get-started 上的說明進行操作。

  1. 讓我們為文章搜尋 API 請求一個金鑰——建立一個新應用,選擇此 API 作為你要使用的 API(填寫名稱和描述,將“文章搜尋 API”下的開關切換到開啟位置,然後點選“建立”)。
  2. 從結果頁面獲取 API 金鑰。
  3. 現在,要開始示例,請複製 nytimes/start 目錄中的所有檔案。如果你已經 克隆了示例儲存庫,那麼你已經擁有這些檔案的副本,你可以在 javascript/apis/third-party-apis/nytimes/start 目錄中找到它們。最初,script.js 檔案包含示例設定所需的一些變數;下面我們將填寫所需的功能。

該應用最終將允許你輸入搜尋詞和可選的開始和結束日期,然後它將使用這些詞來查詢文章搜尋 API 並顯示搜尋結果。

A screenshot of a sample search query and search results as retrieved from the New York Article Search API.

將 API 連線到你的應用

首先,你需要在 API 和你的應用之間建立連線。在此 API 的情況下,你需要在每次從正確 URL 請求服務資料時都將 API 金鑰作為 get 引數包含在內。

  1. 找到以下行
    js
    const key = "INSERT-YOUR-API-KEY-HERE";
    
    將現有的 API 金鑰替換為你在上一步中獲取的實際 API 金鑰。
  2. 將以下行新增到你的 JavaScript 中,“// 事件監聽器控制功能”註釋下方。當提交表單(按下按鈕)時,此行執行名為 submitSearch() 的函式。
    js
    searchForm.addEventListener("submit", submitSearch);
    
  3. 現在新增 submitSearch()fetchResults() 函式定義,在上一行下方

    js
    function submitSearch(e) {
      pageNumber = 0;
      fetchResults(e);
    }
    
    function fetchResults(e) {
      // Use preventDefault() to stop the form submitting
      e.preventDefault();
    
      // Assemble the full URL
      let url = `${baseURL}?api-key=${key}&page=${pageNumber}&q=${searchTerm.value}&fq=document_type:("article")`;
    
      if (startDate.value !== "") {
        url = `${url}&begin_date=${startDate.value}`;
      }
    
      if (endDate.value !== "") {
        url = `${url}&end_date=${endDate.value}`;
      }
    }
    

submitSearch() 函式首先將頁面編號重置為 0 以開始,然後呼叫 fetchResults() 函式。後者首先對事件物件呼叫 preventDefault(),以阻止表單實際提交(這會破壞示例)。接下來,我們使用一些字串操作來組裝我們將傳送請求的完整 URL。我們首先組裝我們認為此演示必需的部分

  • 基本 URL(取自 baseURL 變數)。
  • API 金鑰,必須在 api-key URL 引數中指定(值取自 key 變數)。
  • 頁面編號,必須在 page URL 引數中指定(值取自 pageNumber 變數)。
  • 搜尋詞,必須在 q URL 引數中指定(值取自 searchTerm 文字 <input> 的值)。
  • 要返回結果的文件型別,如透過 fq URL 引數傳遞的表示式中指定。在本例中,我們希望返回文章。

接下來,我們使用幾個 if () 語句來檢查 startDateendDate 元素是否已填充值。如果已填充,我們將它們的值附加到 URL,分別在 begin_dateend_date URL 引數中指定。

因此,完整的 URL 最終看起來像這樣

url
https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=YOUR-API-KEY-HERE&page=0&q=cats&fq=document_type:("article")&begin_date=20170301&end_date=20170312

注意: 您可以在 NYTimes 開發者文件 中找到有關可以包含哪些 URL 引數的更多詳細資訊。

注意: 該示例具有基本的表單資料驗證——必須填寫搜尋詞欄位才能提交表單(使用 required 屬性實現),並且日期欄位已指定 pattern 屬性,這意味著除非它們的值包含 8 個數字(pattern="[0-9]{8}"),否則不會提交。有關這些工作原理的更多詳細資訊,請參閱 表單資料驗證

從 API 請求資料

現在我們已經構建了 URL,讓我們向其傳送請求。我們將使用 Fetch API 來實現。

fetchResults() 函式中,緊靠結束花括號上方新增以下程式碼塊

js
// Use fetch() to make the request to the API
fetch(url)
  .then((response) => response.json())
  .then((json) => displayResults(json))
  .catch((error) => console.error(`Error fetching data: ${error.message}`));

在這裡,我們透過將 url 變數傳遞給 fetch() 來執行請求,使用 json() 函式將響應主體轉換為 JSON,然後將生成的 JSON 傳遞給 displayResults() 函式,以便可以在我們的 UI 中顯示資料。我們還捕獲並記錄可能丟擲的任何錯誤。

顯示資料

好的,讓我們看看我們將如何顯示資料。在 fetchResults() 函式下方新增以下函式。

js
function displayResults(json) {
  while (section.firstChild) {
    section.removeChild(section.firstChild);
  }

  const articles = json.response.docs;

  nav.style.display = articles.length === 10 ? "block" : "none";

  if (articles.length === 0) {
    const para = document.createElement("p");
    para.textContent = "No results returned.";
    section.appendChild(para);
  } else {
    for (const current of articles) {
      const article = document.createElement("article");
      const heading = document.createElement("h2");
      const link = document.createElement("a");
      const img = document.createElement("img");
      const para1 = document.createElement("p");
      const keywordPara = document.createElement("p");
      keywordPara.classList.add("keywords");

      console.log(current);

      link.href = current.web_url;
      link.textContent = current.headline.main;
      para1.textContent = current.snippet;
      keywordPara.textContent = "Keywords: ";
      for (const keyword of current.keywords) {
        const span = document.createElement("span");
        span.textContent = `${keyword.value} `;
        keywordPara.appendChild(span);
      }

      if (current.multimedia.length > 0) {
        img.src = `http://www.nytimes.com/${current.multimedia[0].url}`;
        img.alt = current.headline.main;
      }

      article.appendChild(heading);
      heading.appendChild(link);
      article.appendChild(img);
      article.appendChild(para1);
      article.appendChild(keywordPara);
      section.appendChild(article);
    }
  }
}

這裡有很多程式碼;讓我們一步一步地解釋它

  • while 迴圈是一種常用的模式,用於刪除 DOM 元素(在本例中為 <section> 元素)的所有內容。我們不斷檢查 <section> 是否有第一個子元素,如果有,則刪除第一個子元素。當 <section> 不再有任何子元素時,迴圈結束。
  • 接下來,我們將 articles 變數設定為等於 json.response.docs——這是一個包含所有表示搜尋返回的文章的物件的陣列。這樣做純粹是為了使後面的程式碼更簡單。
  • 第一個 if () 塊檢查是否返回了 10 篇文章(API 每次最多返回 10 篇文章)。如果是,則顯示包含“前 10 個”/“後 10 個”分頁按鈕的 <nav>。如果返回的文章少於 10 篇,則它們都將適合一頁,因此我們不需要顯示分頁按鈕。我們將在下一節中連線分頁功能。
  • 下一個 if () 塊檢查是否沒有返回任何文章。如果是,我們不嘗試顯示任何內容——我們建立一個包含文字“沒有返回結果”的 <p> 並將其插入 <section> 中。
  • 如果返回了一些文章,我們首先建立所有我們想要用於顯示每個新聞故事的元素,將正確的內容插入到每個元素中,然後將它們插入到 DOM 的適當位置。為了確定文章物件中哪些屬性包含要顯示的正確資料,我們查閱了文章搜尋 API 參考(請參閱 NYTimes API)。大多數這些操作都相當明顯,但有一些值得一提
    • 我們使用 for...of 迴圈遍歷與每篇文章關聯的所有關鍵字,並將每個關鍵字插入到它自己的 <span> 內,位於 <p> 中。這樣做是為了便於設定每個關鍵字的樣式。
    • 我們使用 if () 塊(if (current.multimedia.length > 0) { })來檢查每篇文章是否與任何影像相關聯,因為某些故事沒有影像。如果存在,我們只顯示第一張影像;否則,將丟擲錯誤。

連線分頁按鈕

為了使分頁按鈕正常工作,我們將增加(或減少)pageNumber 變數的值,然後使用新的值包含在頁面 URL 引數中重新執行提取請求。這是因為 NYTimes API 每次只返回 10 個結果——如果有多於 10 個結果可用,如果 page URL 引數設定為 0(或根本不包含——0 是預設值),它將返回前 10 個(0-9),如果設定為 1,則返回接下來的 10 個(10-19),依此類推。

這使我們能夠編寫一個簡單的分頁函式。

  1. 在現有的 addEventListener() 呼叫下方,新增這兩個新的呼叫,當單擊相關按鈕時,這些呼叫會導致 nextPage()previousPage() 函式被呼叫
    js
    nextBtn.addEventListener("click", nextPage);
    previousBtn.addEventListener("click", previousPage);
    
  2. 在您之前的新增內容下方,讓我們定義這兩個函式——現在新增此程式碼
    js
    function nextPage(e) {
      pageNumber++;
      fetchResults(e);
    }
    
    function previousPage(e) {
      if (pageNumber > 0) {
        pageNumber--;
      } else {
        return;
      }
      fetchResults(e);
    }
    
    第一個函式增加 pageNumber 變數,然後再次執行 fetchResults() 函式以顯示下一頁的結果。第二個函式以相反的方式工作,幾乎完全相同,但我們還必須額外檢查 pageNumber 是否尚未為零,然後再遞減它——如果提取請求使用負 page URL 引數執行,可能會導致錯誤。如果 pageNumber 已經為 0,我們 return 出函式——如果我們已經在第一頁,則無需再次載入相同的結果。

注意: 您可以在 GitHub 上找到我們的 完成的 NYTimes API 示例程式碼(也可以 在這裡檢視其執行情況)。

YouTube 示例

我們還為您構建了另一個示例供您學習和參考——請參閱我們的 YouTube 影片搜尋示例。它使用了兩個相關的 API

此示例很有趣,因為它展示了兩個相關的第三方 API 如何一起用於構建應用程式。第一個是 RESTful API,而第二個更像 Mapquest(具有 API 特定的方法等)。但是,值得注意的是,這兩個 API 都需要將 JavaScript 庫應用到頁面上。RESTful API 提供了可用於處理發出 HTTP 請求和返回結果的函式。

A screenshot of a sample Youtube video search using two related APIs. The left side of the image has a sample search query using the YouTube Data API. The right side of the image displays the search results using the Youtube Iframe Player API.

我們不會在本文中過多討論此示例——原始碼 中已插入詳細的註釋以解釋其工作原理。

要使其執行,您需要

  • 閱讀 YouTube 資料 API 概述 文件。
  • 確保您訪問了 已啟用 API 頁面,並在 API 列表中,確保 YouTube 資料 API v3 的狀態為“已啟用”。
  • Google Cloud 獲取 API 金鑰。
  • 在原始碼中找到字串 ENTER-API-KEY-HERE,並將其替換為您的 API 金鑰。
  • 透過 Web 伺服器執行示例。如果您只是在瀏覽器中直接執行它(即透過 file:// URL),則它將無法工作。

總結

本文為您提供了使用第三方 API 向網站新增功能的有用介紹。