第三方 API
到目前为止我们已经介绍过的API都是内置于浏览器中的,但并非所有API都是如此。许多大型网站和服务,如Google地图、Twitter、Facebook、PayPal等,都提供了API,允许开发者利用他们的数据(例如,在你的博客上显示你的Twitter信息流)或服务(例如,使用Facebook登录来让你的用户登录)。本文将探讨浏览器API和第三方API之间的区别,并展示后者的一些典型用法。
预备知识 | 熟悉 HTML、CSS 和 JavaScript,尤其是 JavaScript 对象基础知识以及 DOM 脚本和网络请求等核心 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('这是曼彻斯特!')
,它定义了当标记被点击时显示的内容。
最后,我们将 .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上的说明。
- 让我们为“文章搜索API”请求一个密钥——创建一个新应用,选择此API作为您要使用的API(填写名称和描述,将“文章搜索API”下的开关切换到“开”位置,然后点击“创建”)。
- 从结果页面获取API密钥。
- 现在,要开始这个示例,请复制nytimes/start目录中的所有文件。如果你已经克隆了示例仓库,你将已经拥有这些文件的副本,你可以在 javascript/apis/third-party-apis/nytimes/start 目录中找到它们。最初,
script.js
文件包含设置示例所需的一些变量;下面我们将填写所需的功能。
该应用最终将允许您输入搜索词和可选的开始和结束日期,然后它将使用这些信息查询文章搜索API并显示搜索结果。
将API连接到你的应用程序
首先,你需要建立API和你的应用程序之间的连接。对于这个API,你需要在每次从正确的URL服务请求数据时,将API密钥作为get参数包含进去。
-
找到以下这行
jsconst key = "INSERT-YOUR-API-KEY-HERE";
将现有API密钥替换为您在上一节中获取的实际API密钥。
-
在
// Event listeners to control the functionality
注释下方,将以下行添加到您的 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://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()
函数内部,紧接在关闭的花括号之前:
// 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 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)。这些操作中的大多数都相当明显,但有几个值得特别指出:
连接分页按钮
为了使分页按钮工作,我们将增加(或减少)pageNumber
变量的值,然后重新运行包含新值的fetch请求,该值将包含在页面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);
-
在你之前添加的代码下方,让我们定义这两个函数——现在添加这段代码。
jsfunction 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
——如果已经处于第一页,我们就不需要再次加载相同的结果。
注意:您可以在 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密钥。 - 通过网络服务器运行示例。如果您直接在浏览器中运行它(即通过
file://
URL),它将无法工作。
总结
本文为您提供了使用第三方API向您的网站添加功能的实用介绍。