第三方 API

到目前为止我们已经介绍过的API都是内置于浏览器中的,但并非所有API都是如此。许多大型网站和服务,如Google地图、Twitter、Facebook、PayPal等,都提供了API,允许开发者利用他们的数据(例如,在你的博客上显示你的Twitter信息流)或服务(例如,使用Facebook登录来让你的用户登录)。本文将探讨浏览器API和第三方API之间的区别,并展示后者的一些典型用法。

预备知识 熟悉 HTMLCSSJavaScript,尤其是 JavaScript 对象基础知识以及 DOM 脚本网络请求等核心 API 知识。
学习成果
  • 第三方API背后的概念以及相关的模式,例如API密钥。
  • 使用第三方地图API。
  • 使用RESTful API。
  • 使用Google的YouTube 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('这是曼彻斯特!'),它定义了当标记被点击时显示的内容。

最后,我们将 .addTo(map) 链式添加到链的末尾,以实际将标记添加到地图中。

玩转文档中显示的其他选项,看看你能想出什么!Mapquest提供了一些非常高级的功能,例如导航、搜索等。

注意:如果您在运行示例时遇到问题,请对照我们的完成版本检查您的代码。

一个RESTful API——《纽约时报》

现在让我们看另一个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. // Event listeners to control the functionality 注释下方,将以下行添加到您的 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

注意:您可以在《纽约时报》开发者文档中找到有关可包含哪些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 para = 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;
      para.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(para);
      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 APIs)。这些操作中的大多数都相当明显,但有几个值得特别指出:
    • 我们使用了一个for...of循环来遍历与每篇文章关联的所有关键词,并将每个关键词插入到它自己的<span>中,再插入到一个<p>中。这样做是为了方便对每个关键词进行样式设置。
    • 我们使用了一个 if () 块(if (current.multimedia.length > 0) { })来检查每篇文章是否关联了任何图像,因为有些故事没有。我们只在图像存在时显示第一张图像;否则,就会抛出错误。

连接分页按钮

为了使分页按钮工作,我们将增加(或减少)pageNumber变量的值,然后重新运行包含新值的fetch请求,该值将包含在页面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 是否已经为零,然后再递减它——如果 fetch 请求以负数的 page URL 参数运行,可能会导致错误。如果 pageNumber 已经为0,我们就会从函数中 return——如果已经处于第一页,我们就不需要再次加载相同的结果。

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密钥。
  • 通过网络服务器运行示例。如果您直接在浏览器中运行它(即通过 file:// URL),它将无法工作。

总结

本文为您提供了使用第三方API向您的网站添加功能的实用介绍。