news 2026/9/15 10:44:32

JavaScript 调用第三方 API 实战:基于 Fetch 与 Promise 的异步数据获取指南(curriculum 开源课程深度解析)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 调用第三方 API 实战:基于 Fetch 与 Promise 的异步数据获取指南(curriculum 开源课程深度解析)

JavaScript 调用第三方 API 实战:基于 Fetch 与 Promise 的异步数据获取指南(curriculum 开源课程深度解析)

【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum

本文以 open curriculum(cu/curriculum)JavaScript 路径中的 working_with_apis.md 为核心骨架,完整讲解 Web 前端调用第三方 API 的完整链路:从 API 与 API Key 的基本概念、Visual Crossing 天气 API 的首次请求,到用原生fetch替代 XMLHttpRequest、逐层解析 Giphy 的嵌套 JSON 响应,再到用async/await重构 Promise 代码,最后讨论前端密钥暴露风险与服务端环境变量方案。学完本篇,你将能够独立完成"注册 API → 构造请求 URL → 前端拉取数据 → 提取并渲染数据 → 处理错误"的完整实战流程,并具备开发天气应用、GIF 搜索等真实小项目的能力。

课程背景:这一课在整个 JavaScript 学习路径中的位置

该文档位于仓库的 javascript/asynchronous_javascript_and_apis 目录,是"异步 JavaScript 与 API"课程的核心一课,前后分别衔接 asynchronous_code.md(回调与 Promise 基础)与 async_and_await.md(async/await 语法),并以 project_weather_app.md(天气应用项目)作为落地练习。

课程概述明确列出了本篇要解决的四个核心问题:

  • 解释什么是 API。
  • 从宏观上解释如何访问一个 API。
  • 解释如何从 API 获取并提取数据。
  • 解释为什么你的 API 请求可能被浏览器拦截,以及如何修复。

什么是 API:服务器对外开放数据的窗口

API(Application Programming Interface,应用程序编程接口)是服务器为外部使用(网站或应用)提供功能和数据而设计的一套访问接口。为特定网站而建的服务器可以存放博客文章、用户数据、游戏高分等任何内容;也有服务器作为开放服务,向任何想使用的人提供数据(例如天气数据或股票价格)。无论是哪种情况,访问和使用这些数据的方法本质上是一致的

API 通常通过 URL 被访问,而具体如何查询这些 URL,取决于你使用的特定服务。例如,Visual Crossing 的天气 API 提供了多种可请求的数据类型。要获取某个地点的当前天气,可以把城市名直接放入 URL 的路径中:

https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/london

把上述 URL 中的london换成你喜欢的城市并粘贴到浏览器地址栏,你很可能会收到这样一个错误:

No API key or session found. Please verify that your API key parameter is correct.

这个错误引出了 API 使用中最重要的机制之一——API Key(API 密钥)

API Key:访问权限、配额与计费

在大多数情况下,你必须在 API 服务商处创建账号并申请一个 API Key,才能从它们的端点(endpoint,即 API 内用于访问特定功能或数据的专用 URL)获取数据。拿到 Key 之后,通常每次数据请求都要携带它。在 Visual Crossing 中,API Key 以查询字符串参数的形式传入:

https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/london?key=11111111111111111

API Key 的三大作用

  1. 身份标识与用量追踪:API Key 是随机且唯一属于你的。像 Visual Crossing 这样的服务商可以把你的 Key 与你的数据请求(包括请求数量和频率)关联起来,用于统计每个用户的用量。
  2. 滥用防护:签发 API Key 让服务商能够更好地追踪对其系统和数据的滥用行为。
  3. 成本回收与商业变现:服务商可以通过不同档位的付费套餐来回收运行服务器的成本。以 Visual Crossing 为例,免费版允许每天 1000 次调用,但提供的信息有限(对个人小项目来说通常足够);而 Enterprise 版本提供每月无限次 API 调用,并额外提供 Energy 数据、Maritime 数据等高级功能。一次 API 请求的成本或许只有几厘钱,但如果你用它做出了一个全球流行的天气应用,每分钟可能有成千上万人访问数据,流量成本会迅速膨胀——这就是大多数 API 服务商都提供付费档位的原因:付费可以换取更高频的请求配额或更多数据访问权限。

顺带一提:Visual Crossing 官方提供**查询构建器(query builder)**工具,你可以只输入区域来构建查询,并在 "API" 标签页中看到实际生成的查询 URL 结构——这是学习如何构造 API 查询的绝佳辅助工具。

第一次成功请求

前往 Visual Crossing 注册一个免费账号即可获得 API Key(可在账号资料页找到)。把 Key 作为查询字符串参数拼入 URL 后,你应该会收到一个正常的响应,类似下面这样(实际响应比这个示例长得多):

{"queryCost":1,"latitude":51.5064,"longitude":-0.12721,"resolvedAddress":"London, England, United Kingdom","address":"london","timezone":"Europe/London","tzoffset":1.0,"description":"Similar temperatures continuing with a chance of rain tomorrow, Tuesday & Thursday.","days":[{"datetime":"2024-07-06","datetimeEpoch":1720220400,"tempmax":61.4,"tempmin":53.1,"temp":57.8,"feelslikemax":61.4,"feelslikemin":53.1,"feelslike":57.8,"dew":51.3,"humidity":79.7,"precip":0.457,"precipprob":100.0,"precipcover":75.0,"preciptype":["rain"],"snow":0.0,"snowdepth":0.0,"windgust":35.3,"windspeed":21.9,"winddir":262.6,"pressure":1001.8,"cloudcover":70.5,"visibility":8.3,"solarradiation":147.5,"solarenergy":12.9,"uvindex":6.0,"severerisk":10.0,"sunrise":"04:52:02","sunriseEpoch":1720237922,"sunset":"21:18:20","sunsetEpoch":1720297100,"moonphase":0.02,"conditions":"Rain, Partially cloudy","description":"Partly cloudy throughout the day with a chance of rain throughout the day.","icon":"rain","stations":["EGWU","EGLL","D5621","EGLC"]}]}

响应中的days数组包含逐日天气明细(最高/最低温、体感温度、湿度、降水概率、日出日落时间、天气图标等),这正是后续天气应用项目的数据基础。

前置知识:回调与 Promise

在正式动手之前,需要先理解异步编程的两个基础概念,它们在本篇及 asynchronous_code.md 中有详细展开:

  • 回调函数(Callback):作为参数传入另一个函数、并在外层函数内部被调用的函数。例如addEventListener("click", function(){ ... })。回调在简单场景很好用,但一旦需要按顺序链式串联多个异步操作,就会陷入难以维护的"回调地狱(callback hell)"。
  • Promise:一个可能在未来某个时刻产生值的对象。核心价值在于解决"数据还没取回来,代码就继续往下执行"的问题(asynchronous_code.md)。如果getData()返回一个 Promise,你可以这样等待结果:
const myData = getData(); // 如果被重构为返回一个 Promise... myData.then(function(data) { // .then() 告诉它等待 Promise 被 resolve const pieceOfData = data['whatever']; // 然后再执行内部函数 });

.then()的作用是"等 Promise 成功 resolve 后再运行传入的函数",.catch()则用于捕获拒绝(rejected)的 Promise。理解了这两个方法,下面的fetch代码就顺理成章了。

浏览器请求 API 的演进:从 XHR 到 fetch

痛苦的过去:XMLHttpRequest

几年前,在代码中访问 API 数据的主要方式还是XMLHttpRequest。这个函数至今仍在所有浏览器中可用,但用起来非常不友好:

// 光是获取 XHR 就是一团糟! if (window.XMLHttpRequest) { // Mozilla, Safari, ... request = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { request = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } // Open, send. request.open('GET', 'https://url.com/some/url', true); request.send(null);

这段代码既冗长又要处理不同浏览器的历史分支,体验极差。于是开发者开始编写第三方库来简化请求,比较流行的有 axios 和 superagent,它们各有优缺点。然而,浏览器后来实现了原生的 HTTP 请求函数——fetch,这也是本课程选定并推荐的方式。

现在:原生 fetch

// URL(必填),options(可选) fetch('https://url.com/some/url') .then(function(response) { // 请求成功 :) }) .catch(function(err) { // 请求出错 :( });

对比上面的 XHR 代码,fetch 的写法干净得多。注意其中的.then().catch()——这正是前面提到的Promise!fetch 本身返回一个 Promise,因此可以直接链式调用这两个方法。

实战:用 Giphy API 在页面展示随机 GIF

下面我们切换 API,用一个真实可运行的例子把整个流程串起来:调用 Giphy API,在网页上展示一张随机 GIF。Giphy 提供多种搜索和查找 GIF 的方法,这里使用最简单的translate端点。其正确 URL 是api.giphy.com/v1/gifs/translate,需要两个必填参数:

  • api_key:你的 Giphy API Key(需注册 Giphy 开发者账号并免费申请);
  • s:搜索词(search term)。

此外还有可选参数,例如rating可以按内容敏感度过滤结果。把参数拼好(使用你自己的 Key),应该得到类似这样的 URL:

'https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats&rating=g' // 当然是搜索猫啦

在浏览器中打开这个 URL(替换成你的 Key),一切顺利的话会返回一长串 JSON 数据。

搭建页面骨架

为了专注核心逻辑,我们把所有代码放在单个 HTML 文件中:在<body>里放一个空白的<img>标签和一个空的<script>标签。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <img src="#"> <script> </script> </body> </html>

第一步:选中图片元素

在 script 中先选中图片元素并赋值给变量,以便收到数据后替换它的 URL:

<script> const img = document.querySelector('img'); </script>

第二步:发起 fetch 请求

加上前面构造好的 Giphy URL:

<script> const img = document.querySelector('img'); fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats&rating=g') .then(function(response) { console.log(response.json()); }); </script>

打开 HTML 文件,页面上什么都看不到,但控制台里会有一行输出。整个流程中最容易卡住的地方就在这里:response.json()返回的是另一个 Promise!也就是说,要把响应体解析为 JavaScript 对象,还需要再链一个.then()

第三步:解析 JSON 需要二次 .then()

<script> const img = document.querySelector('img'); fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats&rating=g') .then(function(response) { return response.json(); }) .then(function(response) { console.log(response); }); </script>

现在你得到了一个真正的 JavaScript 对象。仔细观察会发现,我们需要的图片 URL 被嵌套在对象相当深的位置。

第四步:逐层穿透嵌套数据

从上面的控制台截图可以看到,Giphy 的响应结构大致为:根对象 →dataimages→ 具体尺寸(如original)→url。要拿到图片地址,必须逐层深入:

<script> const img = document.querySelector('img'); fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats&rating=g') .then(function(response) { return response.json(); }) .then(function(response) { console.log(response.data.images.original.url); }); </script>

第五步:把 URL 赋给图片元素

运行文件,控制台会打印出图片的 URL。最后一步,把页面上<img>src设为这个地址:

<script> const img = document.querySelector('img'); fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats&rating=g') .then(function(response) { return response.json(); }) .then(function(response) { img.src = response.data.images.original.url; }); </script>

一切顺利的话,每次刷新页面都会出现一张新的 GIF 图片——因为你每刷新一次,就向 Giphy 的 translate 端点发起了一次随机查询。至此,你已经完成了"注册 → 构造 URL → fetch → 解析 JSON → 提取嵌套数据 → 渲染到页面"的完整闭环。

深入:用 async/await 重构 Promise 链

Promise 链写起来清晰,但一旦逻辑变多,链式结构依然不够直观。JavaScript 提供了async/await两个关键字,让异步代码读起来更像同步代码,同时保留异步的优势。async_and_await.md 给出了两个行为完全等价的函数示例:一个是.then()链,另一个是async/await写法(async_and_await.md)。

关键知识点(async_and_await.md):

  • async关键字:告诉 JavaScript 引擎你在声明一个异步函数,是函数内使用await的前提。用async声明的函数自动返回一个 Promise——在 async 函数中return等价于 resolve,抛出的错误等价于 reject。本质上,async 函数就是 Promise 的语法糖。
  • await关键字:告诉 JavaScript 等待一个异步动作完成后再继续执行该函数,相当于"暂停直到完成"。它用于在原本要写.then()的地方取出值:不再链式调用.then(),而是把结果直接赋给一个变量。

下面把前面的 Giphy 代码逐步改造成 async/await 风格。由于await不能在非模块脚本的顶层使用,需要先创建一个 async 函数包裹 API 调用。

第 1 步:把 fetch 链包进 async 函数(此时内部仍是 Promise 链):

<script> const img = document.querySelector('img'); async function getCats() { fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats') .then(function(response) { return response.json(); }) .then(function(response) { img.src = response.data.images.original.url; }) .catch(function(error) { console.error(error); }); } </script>

第 2 步:对 fetch 使用 awaitresponse仍是之前传给.then()的那个对象,仍需调用.json(),而.json()又返回 Promise):

<script> const img = document.querySelector('img'); async function getCats() { const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats'); response.json().then(function(response) { img.src = response.data.images.original.url; }).catch(function(error) { console.error(error); }); } </script>

第 3 步:对.json()也使用 await——因为.json()返回 Promise,可以用await把解析结果直接赋给变量:

<script> const img = document.querySelector('img'); async function getCats() { const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats'); const catData = await response.json(); img.src = catData.data.images.original.url; } </script>

第 4 步:用 try...catch 处理错误——不再链式调用.catch(),而是把正常代码放进try块、错误处理放进catch块(async_and_await.md):

<script> const img = document.querySelector('img'); async function getCats() { try { const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats'); const catData = await response.json(); img.src = catData.data.images.original.url; } catch (error) { console.error(error); } } getCats(); </script>

这段代码与上一课基于 Promise 的版本行为完全一致,只是写法更接近同步代码。请始终记住:async/await 只是 Promise 的另一种写法,二者可以随时互相转换。

错误处理的陷阱:fetch 对非 2XX 状态不抛错

一个非常容易踩坑的细节是:只要 API 有响应(哪怕返回404 Not Found或其他非 2XX 状态码),对 fetch 来说都是一次"有效"响应,因此.catch()不会执行.catch()只会在以下情况触发:网络层面失败,或者你后续的 JavaScript 代码抛出了错误(例如访问undefined的属性),或者你手动throw了错误。

所以,如果想针对"API 没有给出期望响应"(例如 404)做条件处理,必须.then()里手动检查。可以借助 Response 对象上的有用属性(如response.okresponse.status)来实现:

fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats') .then(function(response) { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(function(data) { img.src = data.data.images.original.url; }) .catch(function(error) { console.error(error); // 现在 404 等错误也会走到这里 });

课后练习与实战项目

本课作业

  • 查看公开 API 列表,放飞想象力,选择一个感兴趣的 API 练手。
  • 扩展上面的小项目:添加一个按钮,点击即可获取新图片而无需刷新页面。
  • 添加一个搜索框,让用户可以搜索特定的 GIF。
  • 为无效 URL 等场景添加.catch()错误处理(务必结合上文"非 2XX 不抛错"的机制,必要时在.then()中手动抛出错误)。

综合应用:天气应用项目

课程在 project_weather_app.md 中提供了一个完整的综合练习:使用前面学过的 Visual Crossing API 构建一个天气预测网站,要求能够搜索具体地点、并在华氏度与摄氏度之间切换显示。项目还建议:

  • 根据数据改变页面外观(如改变背景色,或用 Giphy API 展示与天气相关的 GIF);
  • Promise 和 async/await 两种风格都尝试使用;
  • 编写"命中 API 的函数"(接收地点并返回天气数据)与"处理 JSON 数据的函数"(只提取应用所需字段并返回新对象)分层设计;
  • 用表单接收用户输入的地点并触发请求;
  • 可选:用 DevTools 模拟网络速度,添加一个"加载中"组件。

这个项目把本课的全部知识点(URL 构造、API Key、fetch、JSON 解析、数据提取、错误处理)整合成一个真实可部署的小应用,是检验掌握程度的试金石。

API Key 安全:为什么不能把密钥写进前端

本课在演示时把 API Key 直接放进了 URL,但这只适用于免费 Key。原因很现实:

  1. 密钥会暴露在客户端:放在前端(浏览器)里的 Key 被视为公开信息。GitHub 上爬取硬编码 API Key 的机器人非常多,恶意者拿到后就能盗用你已经付费的服务和数据——历史上甚至发生过有人因把 AWS 密钥提交到 GitHub 而蒙受巨额损失的著名事故。
  2. 请求会被限流与盗刷:API 可能按次计费或被限流,密钥一旦泄露,别人可以耗尽你的全部配额。
  3. 嗅探风险:Key 直接写在 URL 中,网络流量嗅探者甚至你身后偷看屏幕的人都能轻易获取它。

正确做法:把密钥留在服务端

课程给出的正确方向是:将 API Key 存储在服务器上,通过环境变量提供给应用,绝不发送到前端。这一点在 NodeJS 路径的 environment_variables.md 中有系统讲解:

  • 环境变量是与环境相关、随环境变化的变量,非常适合存储数据库 URL、凭据、API Key 等机密(environment_variables.md);
  • 可以在项目根目录创建.env文件,按NAME=VALUE格式存放密钥,并把.env加入.gitignore,防止提交到公开仓库(environment_variables.md);
  • 在代码中通过 Node 内置的process.env对象读取,例如process.env.WEATHER_API_KEY(environment_variables.md)。

这也是课程反复强调"永远不要信任客户端"的原因——不仅不能信任来自客户端的数据,也不能信任发送给客户端的任何内容(包括 API Key)。GitHub 检测到公开仓库中提交了 API Key 时会发出安全警报,这正是为了提醒你这一风险。

不过,在目前的前端阶段,这一安全话题大多可以先放一放:本课程使用的是免费 API 访问,应用也主要供自己和看作品集的人使用。服务端密钥管理会在 Full Stack JavaScript 路径的后端课程中详细覆盖;在 Ruby 课程中,如果你还没学到,后面也会讲到。

知识自检

  • 什么是 API?——服务器对外提供数据和功能的接口,通常通过 URL 访问。
  • 如何限制对 API 的访问?——通过 API Key:注册账号获取密钥,每次请求携带密钥,服务商据此追踪用量、防滥用并计费。
  • 如何从 API 获取并提取数据?——用fetch发起请求,.then(response => response.json())解析 JSON(或使用async/awaitconst data = await response.json()),再通过属性访问逐层提取嵌套数据。

延伸阅读:本文核心内容出自 working_with_apis.md,前置概念详见 asynchronous_code.md,async/await 重构详见 async_and_await.md,综合练习见 project_weather_app.md,服务端密钥管理可继续学习 environment_variables.md。

【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 10:43:41

MCP for Unity 提示 uv Not Found:uvx 启动不了服务器怎么修?

MCP for Unity 提示 uv Not Found&#xff1a;uvx 启动不了服务器怎么修&#xff1f; 【免费下载链接】unity-mcp Unity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control scenes, edit scripts, and automa…

作者头像 李华
网站建设 2026/9/15 10:42:01

JavaScript实现2048军旗版:二维数组与移动合并算法全解析

简介&#xff1a;一份基于JavaScript实现的2048军旗版游戏完整源码包&#xff0c;面向Web前端初学者和游戏开发入门者&#xff0c;可帮助理解数字拼图游戏从棋盘建模到交互响应的完整实现&#xff0c;也适用于课程设计、期末作业或想要在经典2048基础上增加自定义玩法的改版参考…

作者头像 李华
网站建设 2026/9/15 10:41:47

从React到Astro:用岛架构消除静态页面的JavaScript负担

我最近在一个维护了很久的 React 博客项目里做了一件很多人看来很“激进”的事&#xff1a;把页面里绝大多数 React 组件删掉&#xff0c;换成了 Astro 来做静态站点生成。不是 React 不行&#xff0c;而是我意识到&#xff0c;在“几乎没有用户交互”的页面里&#xff0c;让浏…

作者头像 李华
网站建设 2026/9/15 10:39:11

SAP Gateway Service Tagging 深入解析,给 OData 服务目录建立一套可搜索的语义索引

在一个运行时间足够长的 SAP S/4HANA 系统里,OData 服务数量通常会越来越多。最早可能只有几十个服务,后来随着 SAP Fiori 应用、自定义 UI5 应用、移动端、外围系统集成以及各种扩展需求不断增加,服务目录很容易膨胀到几百甚至更多。 到了这个阶段,一个很现实的问题会冒出…

作者头像 李华