第三方 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
对象访问的。例如
const audioCtx = new AudioContext();
// …
const audioElement = document.querySelector("audio");
// …
const audioSource = audioCtx.createMediaElementSource(audioElement);
// etc.
另一方面,第三方 API 位于第三方服务器上。要从 JavaScript 访问它们,你首先需要连接到 API 功能并使其在你的页面上可用。这通常涉及首先通过 <script>
元素链接到服务器上提供的 JavaScript 库,如我们的 Mapquest 示例所示
<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" />
然后,你可以开始使用该库中提供的对象。例如
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 示例中找到类似以下内容的行
L.mapquest.key = "YOUR-API-KEY-HERE";
此行指定在你的应用程序中使用的 API 或开发者密钥——应用程序的开发者必须申请获取密钥,然后将其包含在他们的代码中才能获得对 API 功能的访问权限。在我们的示例中,我们只提供了一个占位符。
注意:创建你自己的示例时,你将在任何占位符的位置使用你自己的 API 密钥。
其他 API 可能要求你以稍微不同的方式包含密钥,但对于大多数 API 来说,模式都比较相似。
要求密钥使 API 提供商能够追究 API 用户对其行为的责任。当开发者注册了密钥后,API 提供商就会知道他们,如果他们开始对 API 做任何恶意的事情(例如跟踪人们的位置或尝试用大量请求轰炸 API 以使其停止工作),就可以采取行动。最简单的行动就是撤销他们的 API 权限。
扩展 Mapquest 示例
让我们为 Mapquest 示例添加更多功能,以展示如何使用 API 的其他一些功能。
- 要开始本节,请在新的目录中复制 mapquest 初始文件。如果你已经 克隆了示例存储库,那么你已经拥有该文件的副本,你可以在 javascript/apis/third-party-apis/mapquest/start 目录中找到它。
- 接下来,你需要访问 Mapquest 开发者网站,创建一个帐户,然后创建一个开发者密钥以用于你的示例。(在撰写本文时,它在网站上被称为“消费者密钥”,密钥创建过程还要求提供可选的“回调 URL”。你无需在此处填写 URL:只需将其留空即可。)
- 打开你的起始文件,并将 API 密钥占位符替换为你的密钥。
更改地图类型
可以使用 Mapquest API 显示多种不同类型的地图。为此,请找到以下行
layers: L.mapquest.tileLayer("map");
尝试将 'map'
更改为 'hybrid'
以显示混合样式地图。也尝试一些其他值。 tileLayer
参考页面 显示了不同的可用选项,以及更多信息。
添加不同的控件
地图提供了一些不同的控件;默认情况下,它只显示缩放控件。你可以使用 map.addControl()
方法扩展可用的控件;将其添加到你的代码中
map.addControl(L.mapquest.control());
mapquest.control()
方法 只创建一个简单的功能齐全的控件集,默认情况下它位于右上角。你可以通过指定一个包含 position
属性的选项对象作为控件的参数来调整位置,该属性的值是一个字符串,指定控件的位置。例如,试试这个
map.addControl(L.mapquest.control({ position: "bottomright" }));
还有其他类型的控件可用,例如 mapquest.searchControl()
和 mapquest.satelliteControl()
,其中一些非常复杂且功能强大。尝试一下,看看你能想出什么。
添加自定义标记
在地图上的某个点添加标记(图标)很容易——你只需使用 L.marker()
方法(似乎在相关的 Leaflet.js 文档中进行了说明)。将以下代码添加到你的示例中,同样在 window.onload
内部
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 上的说明进行操作。
- 让我们为文章搜索 API 请求一个密钥——创建一个新应用,选择此 API 作为你要使用的 API(填写名称和描述,将“文章搜索 API”下的开关切换到开启位置,然后点击“创建”)。
- 从结果页面获取 API 密钥。
- 现在,要开始示例,请复制 nytimes/start 目录中的所有文件。如果你已经 克隆了示例存储库,那么你已经拥有这些文件的副本,你可以在 javascript/apis/third-party-apis/nytimes/start 目录中找到它们。最初,
script.js
文件包含示例设置所需的一些变量;下面我们将填写所需的功能。
该应用最终将允许你输入搜索词和可选的开始和结束日期,然后它将使用这些词来查询文章搜索 API 并显示搜索结果。
将 API 连接到你的应用
首先,你需要在 API 和你的应用之间建立连接。在此 API 的情况下,你需要在每次从正确 URL 请求服务数据时都将 API 密钥作为 get 参数包含在内。
- 找到以下行将现有的 API 密钥替换为你在上一步中获取的实际 API 密钥。js
const key = "INSERT-YOUR-API-KEY-HERE";
- 将以下行添加到你的 JavaScript 中,“
// 事件监听器控制功能
”注释下方。当提交表单(按下按钮)时,此行运行名为submitSearch()
的函数。jssearchForm.addEventListener("submit", submitSearch);
- 现在添加
submitSearch()
和fetchResults()
函数定义,在上一行下方jsfunction 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 ()
语句来检查 startDate
和 endDate
元素是否已填充值。如果已填充,我们将它们的值附加到 URL,分别在 begin_date
和 end_date
URL 参数中指定。
因此,完整的 URL 最终看起来像这样
https:/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()
函数中,紧靠结束花括号上方添加以下代码块
// 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()
函数下方添加以下函数。
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)。大多数这些操作都相当明显,但有一些值得一提
连接分页按钮
为了使分页按钮正常工作,我们将增加(或减少)pageNumber
变量的值,然后使用新的值包含在页面 URL 参数中重新运行提取请求。这是因为 NYTimes API 每次只返回 10 个结果——如果有多于 10 个结果可用,如果 page
URL 参数设置为 0(或根本不包含——0 是默认值),它将返回前 10 个(0-9),如果设置为 1,则返回接下来的 10 个(10-19),依此类推。
这使我们能够编写一个简单的分页函数。
- 在现有的
addEventListener()
调用下方,添加这两个新的调用,当单击相关按钮时,这些调用会导致nextPage()
和previousPage()
函数被调用jsnextBtn.addEventListener("click", nextPage); previousBtn.addEventListener("click", previousPage);
- 在您之前的添加内容下方,让我们定义这两个函数——现在添加此代码第一个函数增加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
- YouTube 数据 API 用于搜索 YouTube 视频并返回结果。
- YouTube IFrame 播放器 API 用于在 IFrame 视频播放器中显示返回的视频示例,以便您可以观看它们。
此示例很有趣,因为它展示了两个相关的第三方 API 如何一起用于构建应用程序。第一个是 RESTful API,而第二个更像 Mapquest(具有 API 特定的方法等)。但是,值得注意的是,这两个 API 都需要将 JavaScript 库应用到页面上。RESTful API 提供了可用于处理发出 HTTP 请求和返回结果的函数。
我们不会在本文中过多讨论此示例——源代码 中已插入详细的注释以解释其工作原理。
要使其运行,您需要
- 阅读 YouTube 数据 API 概述 文档。
- 确保您访问了 已启用 API 页面,并在 API 列表中,确保 YouTube 数据 API v3 的状态为“已启用”。
- 从 Google Cloud 获取 API 密钥。
- 在源代码中找到字符串
ENTER-API-KEY-HERE
,并将其替换为您的 API 密钥。 - 通过 Web 服务器运行示例。如果您只是在浏览器中直接运行它(即通过
file://
URL),则它将无法工作。