第三方 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 向网站添加功能的有用介绍。