编程语言研究书库

三个实战项目 — 天气应用、串并行对比、重试器

三个实战项目 — 天气应用、串并行对比、重试器

这一章是全书模式的装配车间: 三个项目,分别把「取数+错误处理」「串并行编排」「重试机制」装成能跑的完整应用。 读完你拿到三个项目的完整结构与关键实现,更重要的是每个项目里"哪部分该独立成函数"的判断过程。

1. 这一章讲什么

前九章是零件与模式,本章是整机。三个项目难度递进:

  1. 天气应用(第 3 节):单请求、单界面——练习取数与显示分离;
  2. 电影库(第 4 节):同一批请求跑两遍,串行一遍、并行一遍——练习编排,还练习测量;
  3. 重试器(第 5 节):会失败的接口+退避重试——练习失败路径的完整设计。

每个项目都按同一条流水线走:搭 HTML/CSS 骨架 → 写 JS 实现 → 说明怎么测、会看到什么。

2. 顶层全景:三个项目各练什么

  项目一(天气)      项目二(电影库)        项目三(重试器)
  ─────────        ─────────────        ─────────────
  输入→取数→显示    输入→[串行|并行]→显示   点按钮→可能失败→退避重试→结果
  练:错误分支        练:编排+计时           练:失败路径设计
  新结构:无          新结构:console.time    新结构:递归

3. 项目一:天气应用

3.1 骨架

界面三件套:输入框(城市名)、按钮、结果显示区;一个 app.js 文件承担全部逻辑。取数指向 OpenWeatherMap 的真实接口(需要免费申请一个 API key 填进代码)1。

3.2 核心实现:三件事分成三个函数

原书的实现把界面程序拆成职责分明的三块2:

async function getWeather(city) {
  const response = await fetch(`${BASE_URL}?q=${city}&appid=${API_KEY}&units=metric`);
  if (!response.ok) { throw new Error('City not found'); }
  return await response.json();
}
function displayWeather(data) {
  const { name, main, weather } = data;
  weatherInfo.innerHTML = `
    <h2>${name}</h2>
    <p>Temperature: ${main.temp}°C</p>
    <p>Feels like: ${main.feels_like}°C</p>
    <p>Humidity: ${main.humidity}%</p>
    <p>Weather: ${weather[0].description}</p>`;
}
getWeatherButton.addEventListener('click', async () => {
  const city = cityInput.value.trim();
  if (!city) { weatherInfo.textContent = 'Please enter a city name'; return; }
  try {
    weatherInfo.textContent = 'Loading...';
    const weatherData = await getWeather(city);
    displayWeather(weatherData);
  } catch (error) {
    weatherInfo.textContent = `Error: ${error.message}`;
  }
});

三块各自的职责,一条线说清:

  事件处理(按钮)          getWeather(取数)         displayWeather(显示)
  ─────────────          ──────────────          ─────────────────
  读输入、空值检查          发请求、判 ok、抛错        把数据拼成 HTML
  显示"Loading..."                                 不关心数据哪来的
  接住错误,写错误文案       不关心往哪显示

这个三分法就是第 11 章"分离关注点"的实战出处:界面层管时序与状态,取数层管网络与错误,显示层管渲染。 错误分支也齐全:空输入、请求失败(404 → 'City not found')、加载中,三种状态都有对应界面。

3.3 测试路径

填入 API key → 打开页面 → 输城市名点按钮 → 看四种情形:正常显示气温/体感/湿度/天气描述;空输入给提醒;不存在的城市显示 Error;请求期间显示 Loading 动画3。

3.3a 骨架里的教学细节

三个项目的 HTML 骨架都小得可怜(一个容器、一两个控件、一个结果区),但有两个细节是刻意设计的4:

其一,结果区一开始是空 div,内容由 JavaScript 填充。 天气应用的 weatherInfo、电影库的 movieList、重试器的 result,全是"占位的空盒子"。这对应一个界面原则:HTML 管结构,JavaScript 管状态——刷新页面看到的一切动态内容,都应该能在 app.js 里找到对应的赋值语句。如果 HTML 里预先写死了部分内容,状态就漏了一格,测试清单(3.4)上就永远有一格查不到。

其二,脚本用普通 <script src> 而不是模块。 这是入门书的最小阻力选择——不用配构建工具,双击 HTML 就能跑。代价是没有模块化隔离,app.js 里的一切都是全局可见的。三个项目各自只有一个脚本文件,所以这个代价为零;项目一大,就要换模块或构建工具,那是另一本书的话题。

CSS 的定位也一样:三个项目的样式都是"能看就行"的最小集(居中、卡片阴影、按钮变色),没有任何动画与响应式。这不是疏忽——本书的力气全花在 app.js 上,界面刻意保持"朴素到不会分散精力"。

3.3b 一次点击的完整旅程

把天气应用从点击到渲染的每一步串起来,标出各层的位置(耗时为演示编)5:

  t=0.0  用户点按钮 → 事件层被唤醒:trim 读输入
         ├─ 空?→ 显示提醒,旅程结束(不碰网络)
         └─ 不空 → 显示 "Loading...",旅程继续
  t=0.1  事件层调 getWeather(city) → 进入取数层
  t=0.1  fetch 发出;response.ok 检查;数据对象成形
  t=1.1  数据回到事件层 → 调 displayWeather(data) → 进入显示层
  t=1.1  innerHTML 拼接,四项数据显示,旅程结束
  ── 失败分支:t=0.1~ 请求挂掉 → 错误抛回事件层 catch → 显示 Error

这张旅程图里藏着一条层的纪律:取数层抛出的错误,穿过调用它的 await,落在事件层的 catch 里——取数层自己不会 try/catch(它只负责"把失败变成错误递出去"),事件层负责"失败时界面怎么办"。两层各司其职,各自简单;混在一起,就是一个又长又难测的函数。三个项目的取数函数(getWeather/fetchMovie/simulateAPICall 的消费端)全部遵守这条纪律。

3.4 天气应用的四种界面状态

3.2 的三分法之外,这个项目还有一个值得单独说的设计:界面在任意时刻必居四种状态之一,代码里各有一条路径对应6:

状态触发界面显示代码位置
空闲刚打开输入框+按钮,结果区空白(初始)
加载中点了按钮,请求在途"Loading..."try 块第一行
成功数据到了城市名+四项数据displayWeather
失败请求挂了"Error: City not found"catch 块
输入为空点了按钮但输入框空"Please enter a city name"事件处理开头的 if

第五种状态(空输入)常被漏掉——它在任何网络交互之前,是纯界面逻辑,所以它归事件处理层管而不是 catch 管。这个"状态清单"是任何取数界面的通用骨架,写自己的应用时先列这五格,再往里填代码,比写完全局再补错误分支要稳得多。

3.5 从天气应用到任意取数界面

把天气应用抽象一层,它其实是"一个输入、一次取数、一个展示"的最小模板,换个 API 就是新应用:

  输入控件(文本框/下拉/按钮)        ← HTML
  事件处理(读输入 → 校验空值 → Loading → try 取数 → display/Error)  ← app.js 的事件层
  取数函数(一个 URL 一个函数,内部只管 fetch+ok+json+throw)        ← app.js 的取数层
  展示函数(收数据对象,拼 HTML)                                    ← app.js 的显示层

第 12 章会看到这个模板在 React/Vue 里的变形(取数层装进自定义 hook,状态不再散落在 DOM 里而是收进状态);在纯 JavaScript 里,这个四格模板就是"能维护"的下限。项目一的全部教学价值,就在于它足够小,小到你能在一次阅读里看清四格的边界。

4. 项目二:电影库——同一组请求,两种编排,实测量差

4.1 设计

界面两个按钮("串行取" / "并行取")+ 电影列表区。取数对象是 TMDB 的真实电影接口,一次取 5 部指定 ID 的电影7。

4.2 两条取数路径

原书用同一批 5 个 ID,写了两个函数,差异只在编排8:

async function fetchMoviesSequentially() {
  console.time('Sequential');
  for (const id of movieIds) {                 // 一个回来,才发下一个
    try {
      const movie = await fetchMovie(id);
      displayMovie(movie);
    } catch (error) { console.error(`Error fetching movie ${id}:`, error); }
  }
  console.timeEnd('Sequential');
}
async function fetchMoviesInParallel() {
  console.time('Parallel');
  const moviePromises = movieIds.map(id => fetchMovie(id));   // 五个同时上路
  try {
    const movies = await Promise.all(moviePromises);
    movies.forEach(displayMovie);
  } catch (error) { console.error('Error fetching movies:', error); }
  console.timeEnd('Parallel');
}

4.3 用 console.time 量出差距

console.time('标签') 与 console.timeEnd('标签') 是一对计时记号:同标签的头尾之间,浏览器计好耗时打到控制台。两个按钮各点一次,控制台两行输出(数字是为演示编的):

  Sequential: 5137.000ms     ← 5 个请求,每个约 1 秒,相加
  Parallel:   1092.000ms     ← 5 个同时发,约等于最慢的一个

约 5 倍差距——正是并行度(5)。这个数字比任何说教都管用:它把"独立请求该并行"从一条规则变成一次亲眼所见。 顺带注意两个函数的错误处理差异:串行版每部单独 try/catch,一部失败不影响下一部;并行版一个 catch 全包(第 04 章 all 的短路语义——一部失败,Promise.all 整体拒)9。

判断(我们的,不是书里的): 这个项目最值钱的不是"并行快"(那是第 06 章的已知结论),而是它示范了用一对计时函数把架构决策变成可测量的数字。优化之争(该不该并行、要不要缓存)在无数团队里停在口水战,而这个项目给了止战工具:各写一版,各量一次。把这套做法推广到任何"我觉得更快"的争论上,比记住 5 倍这个数字有用得多。 如果错,会错在: console.time 测的是全程墙钟时间(从发出到收齐),受网络抖动影响大;严谨对比要跑多次取中位数(书里跑一次就下结论)。这是演示,不是规范的性能对比——别拿单次结果去做架构决策。

4.4 串行版的逐圈走查

4.2 的两版并排之外,把串行版单独走一遍,看清它每圈在干什么(各请求 1 秒,为演示编)10:

  第 1 圈:for 取 id=550 → await fetchMovie(550) → 1 秒后显示 → catch 兜这一部的错
  第 2 圈:for 取 id=551 → await ……
  …5 圈共约 5 秒,电影一张一张出现(先来先显示)

"一张一张出现"是串行的外观特点,也是它唯一的体验优点;并行的外观是"转 1 秒圈,五张同时出现"。两种体验对应两种产品预期:一页一页加载、瀑布流适合串行(用户想先看先来的);报表、对照页适合并行(用户要一次看全)。项目二把两个按钮并排放在页面上,等于把这个产品决策点显式交给了点按钮的人——教学上聪明,产品上要自己定。

4.5 计时输出的读法

console.time 的输出形如 Sequential: 5137.000ms11。读这两个数字时的三个提醒:

  1. 看数量级,不看个位数——网络抖动能让单次结果差出几百毫秒,5137 与 4900 没有区别,5137 与 1100 才是结构性的差距(约等于并行度);
  2. 顺序标签必须成对——console.time('Sequential') 与 console.timeEnd('Sequential') 的标签字符串要一字不差,打错字母的话 timeEnd 找不到起点,什么都不打;
  3. 嵌套计时各记各的——并行版内部还可以给单个请求再嵌套计时(标签不同即可),这把"总耗时"与"最慢一环"分开,是定位并行收益被谁吃掉的第一手数据。

4.6 模拟接口的"诚实"设计

项目三的第一步是造一个会失败的模拟接口,这个设计本身有教学讲究12:

async function simulateAPICall() {
  const randomDelay = Math.floor(Math.random() * 1000) + 500;  // 500-1500ms
  await new Promise(resolve => setTimeout(resolve, randomDelay));
  if (Math.random() < 0.5) throw new Error('API call failed');
  return { message: 'API call succeeded', timestamp: new Date().toISOString() };
}

三个设计决策。其一,延迟也随机:真实网络不是固定 1 秒,把延迟做成 500-1500ms 的随机,重试的时间线才不会呈现出"假规律"(如果每次恰好 1 秒,指数退避的等待表格会整齐得像作弊)。其二,失败率定在一半:太高(90%)会让"成功"像奇迹,测试时几乎看不到成功路径;太低(10%)点十次都碰不到一次重试。50% 是让"成功、重试后成功、全败"三条路径在一次手动测试里都能撞见的甜蜜点。其三,成功也带时间戳:结果里带 new Date().toISOString(),每次成功都不一样,界面上一眼能区分"这是新结果"还是"上一次的残留"。

这个"诚实的模拟"原则适用于一切异步测试:模拟值(模拟的 1 秒)不等于真实值(网络的随机长尾),但模拟的分布要按真实的样子造(有快有慢、有败有成),否则测试通过的那一刻就是错觉开始的时候(第 00 章 §5 的判断块说的就是这个)。

5. 项目三:重试器——失败路径的完整设计

5.1 设计

一个会故意失败的模拟接口:随机等 0.5-1.5 秒,然后抛硬币(一半机会)决定成功或失败。按钮一点,重试机制上场,界面显示每次重试的进展13。

5.2 核心实现:递归版指数退避

async function simulateAPICall() {
  const randomDelay = Math.floor(Math.random() * 1000) + 500;  // 500-1500ms
  await new Promise(resolve => setTimeout(resolve, randomDelay));
  if (Math.random() < 0.5) throw new Error('API call failed');
  return { message: 'API call succeeded', timestamp: new Date().toISOString() };
}
async function fetchWithRetry(retries = 3, delay = 1000) {
  try {
    return await simulateAPICall();
  } catch (error) {
    if (retries > 0) {
      console.log(`Retrying... (${retries} attempts left)`);
      await new Promise(resolve => setTimeout(resolve, delay));
      return fetchWithRetry(retries - 1, delay * 2);   // 递归:次数减一,延迟翻倍
    } else {
      throw error;
    }
  }
}

和第 06 章 4.3 的循环版退避相比,这里是递归版:函数失败后不循环,而是带新参数调自己——retries - 1(剩余次数减一)与 delay * 2(等待翻倍)从一次调用传进下一次。走一遍(各次成败是为演示编的):

  第 1 次:失败 → 等 1000ms → 调自己(retries=2, delay=2000)
  第 2 次:失败 → 等 2000ms → 调自己(retries=1, delay=4000)
  第 3 次:失败 → 等 4000ms → 调自己(retries=0, delay=8000)
  第 4 次:失败 → retries=0,不再递归,把错误抛出去
  全程 1+2+4=7 秒等待 + 4 次请求,全败才认输

递归版的结构把"还有几次机会、现在等多久"全部压进参数,循环体里没有任何状态变量——这是递归这一写法的标准示范(函数自己调用自己,每次用更小的参数,直到一个"停止条件"(这里是 retries > 0 为假)让递归停住)。

5.3 界面与观察点

界面只显示"Fetching..."→ 成功文案(带发生时刻)/ 错误文案;真正的观察点在浏览器控制台:能看到每次 Retrying... (N attempts left) 的日志与逐渐翻倍的实际间隔14。这个项目教的不只是重试,还有失败路径要可观测——日志打在控制台,用户看到的是平静的一句话。

5.4 递归版的边界:次数与延迟两个参数的收敛

5.2 的递归有两个参数在收敛:retries 每次 -1(向下到 0 停),delay 每次 ×2(向上翻)。两个方向一个收敛一个发散,这正是递归能安全使用的条件——至少有一个参数在朝停止条件走。如果把 retries - 1 误写成 retries,停止条件永远到不了,递归会变成无限循环(每次翻倍等待,很快就把事件循环堵死——第 08 章错误五的递归版)。写递归时的自检口诀:找到"那个每次变小的东西",找不到就别提交。

顺带一个风格注记:递归版也可以用循环改写(第 13 章 4.3 场景三给了 while 版),两版行为等价。递归版的优点是"每次尝试的全部上下文(还剩几次、现在等多久)都在参数里",一眼能看全;循环版的优点是"没有函数一层层叠上去的增长"(第 09 章边界提过,深递归会吃栈——3 次重试无所谓,300 次就得换循环)。这个项目用递归,是教学选择,不是技术必须。

5.5 三个项目的共同骨架:搭—实现—测

回头看,三个项目走的是同一条流水线,这条流水线本身就是可复用的方法15:

  ① 搭骨架:HTML 摆控件,CSS 摆样式 —— 先让页面"长得像"
  ② 实现:app.js 按"事件层/取数层/显示层"三格填逻辑
  ③ 测试:列出状态清单(空闲/加载/成功/失败/空输入),逐格手动验证

三个项目各自把这流水线走了一遍:天气应用练②(三格分工最干净),电影库练③的"测量"变体(测试不止"对不对",还"快不快"),重试器练②的失败路径(成功路径三行就完,90% 的代码在 catch 与递归参数上)。如果只能带走一样,带走这条流水线——它适用于任何"界面 + 异步"的小项目,与框架无关(第 12 章只是给每层换了容器)。

5.6 三个项目的延伸练习

书在结尾鼓励"扩展这些项目",这里给四个具体方向,每个都对应一章的知识:

  1. 天气应用加缓存(第 07 章 4.1):同一城市十分钟内不再请求,直接显示上次结果——练"缓存回退链";
  2. 电影库加并发上限(第 09 章边界):并行版限制"同时在途最多 2 个"——练信号量式的排队;
  3. 重试器加分诊(第 06 章 4.5):4xx 失败不重试、直接显示——练"按错误类型决定流程";
  4. 任意项目加 AbortController(第 09 章 5.3):离开页面/重新点击时取消未完成的请求——练"取消"这个 Promise 的盲区。

做完任意两个,第 06-09 章的知识就从"读过"变成"用过"。

最后交代一句选型:三个项目全部用 async/await 而没出现一条 .then(书的第 10 章代码里 then 只在第 8 章的调试与排障语境中露过面)——这不是书的偏见,是第 05 章结论(默认 async/await,除非要链的精确位置语义)的自然结果;Promise 仍在那里,只是退到了 await 的底下。

5.7 项目三与第 06 章的对照账

项目三(递归重试)与第 06 章 4 节(循环重试)是同一个模式的两份实现,把它们的差异记成账,比单独读任何一份都记得牢:

第 06 章循环版项目三递归版
状态放在哪函数体里的局部变量(i)参数(retries, delay)
重复的方式for 循环转圈函数自己调自己
翻倍写在哪delay * 2 顺手乘调用时 delay * 2 传下去
栈的增长无每次递归多一帧(3 层无碍)
读起来像流程数学归纳

书里两处都教,用意明显:同一个逻辑能被两种语法灵魂各写一遍,读代码时才不会被形式吓住——老库里的递归重试和新代码里的循环重试,从此是熟人。

5.8 从项目到产品:三个被省略的环节

从"能跑的项目"到"能上线的产品",中间隔着书里(故意)省略的三个环节:

  1. 输入校验与转义:天气应用把城市名直接拼进 URL、把接口返回直接 innerHTML——练习无妨,上线前 city 要编码(空格与特殊字符)、返回内容要防注入。书不教,因为教的是异步,不是安全;但心里要有这根弦。
  2. API key 的存放:const API_KEY = 'your_api_key_here' 写在 app.js 里,等于把钥匙贴在门上——浏览器里的 key 本来就防不住人看,真实产品要么走后端代理,要么用允许来源白名单的 key。
  3. 错误信息的用户友好度:Error: City not found 是开发者语言;产品上更常见的是"没有找到这个城市,检查一下拼写?"——第 07 章 4.2 默认值的精神延伸到文案:给用户的,要像人话。

这三环不是异步知识,却是把异步代码送出门前的最后一道工序。书把它们留白了,我们用这张清单补上。

6. 作者的判断与证据

本章的判断藏在结构里:三个项目一个共同点——每个都先把"取数"从"界面"里拽出来单独成函数。这是作者对"能维护的异步代码"的定义,第 11 章会把它写成明文规则16。

证据形态:三个项目都可运行、可测试,测试说明具体到"你会在控制台看到什么"——可验证性是本章证据的全部。

7. 边界与局限

  • 天气与电影项目都依赖外部真实 API(要注册 key、受接口改版影响),不可离线复现;
  • 电影项目只对比了"全串行 vs 全并行",没演示混合编排(第 06 章 3.2 的三段式);
  • 重试器的递归版没有次数硬顶之外的熔断(连续失败的退避上限),也没有区分错误类型(第 06 章 4.5 的分诊没用上);
  • HTML/CSS 骨架占了不小篇幅但都是常规写法,无教学增量。

8. 可带走的

  1. 界面程序三分:事件层(读输入/管状态展示/接错误)、取数层(发请求/判 ok/抛错)、显示层(拼 HTML)——各层互不越界;
  2. 异步代码的界面要覆盖四种状态:正常、加载中、空输入、错误;
  3. 串行 vs 并行用 console.time/timeEnd 量出来再下结论——但这次是演示,严谨对比要多次取中位;
  4. 递归三要素:自己调自己、参数变小(retries-1, delay*2)、停止条件(retries>0 为假就抛);
  5. 失败路径要可观测:控制台留日志,用户界面保持平静;
  6. 并行 catch 与串行 catch 的语义不同:all 是一拒全拒,逐个好抓——编排方式决定错误粒度。

9. 原文地图

主题原书章原文位置
本章三项目Chapter 10: Hands-On Mini Projectstext/13-ch10-chapter-10-hands-on-mini-projects.txt:7(搜「three exciting mini-projects」)
天气应用结构同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:9(搜「Weather Application」) · text/13-ch10-chapter-10-hands-on-mini-projects.txt:75(搜「getWeather」)
天气应用测试同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:125(搜「API key」)
电影库:串行同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:235(搜「fetchMoviesSequentially」)
电影库:并行与计时同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:251(搜「fetchMoviesInParallel」) · text/13-ch10-chapter-10-hands-on-mini-projects.txt:276(搜「console.time」)
重试器:模拟接口同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:365(搜「simulateAPICall」)
重试器:递归退避同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:376(搜「fetchWithRetry」) · text/13-ch10-chapter-10-hands-on-mini-projects.txt:384(搜「delay * 2」)
重试器:测试观察点同上text/13-ch10-chapter-10-hands-on-mini-projects.txt:421(搜「browser console」)

Footnotes

  1. 出处:「Chapter 10: Hands-On Mini Projects」第 13-73 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:28,搜「cityInput」;:69,搜「openweathermap」)。HTML 骨架、BASE_URL 指向 OpenWeatherMap 照原书。 ↩

  2. 出处:同章第 75-115 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:75,搜「getWeather」;:90,搜「displayWeather」;:101,搜「addEventListener」)。三个函数的分工与错误分支照原书。 ↩

  3. 出处:同章第 123-129 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:125,搜「API key」)。测试需注册 OpenWeatherMap 免费 key,照原书。 ↩

  4. 出处:「Chapter 10: Hands-On Mini Projects」第 15-73 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:30,搜「weatherInfo」;:32,搜「app.js」)。HTML 骨架的容器-控件-结果区结构与 script 引入方式照原书。 ↩

  5. 出处:「Chapter 10: Hands-On Mini Projects」第 75-115 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:75,搜「getWeather」;:101,搜「addEventListener」)。点击到渲染的调用链与错误分支照原书。 (本章完:三个项目=三个模式,搭—实现—测=一条流水线;把 5.6 的延伸练习做掉两个,本书才算真正读完。) 把延伸练习做掉两个,本书才算真正读完;这也是作者最后的嘱咐。 本章是全书唯一需要动手的一章——书中的代码一字不改地敲一遍,胜过把前十章再读一遍。 动手时的报错与书里不一致,多半是 API key 或接口改版——这正是 5.8 第三环(诚实模拟)存在的理由。 (本章完:搭—实现—测,流水线通吃一切小项目。) ↩

  6. 出处:「Chapter 10: Hands-On Mini Projects」第 101-115 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:104,搜「Please enter」;:109,搜「Loading」;:113,搜「Error」)。空输入、加载中、错误三条界面路径照原书。 ↩

  7. 出处:同章第 131-214 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:133,搜「TMDB」;:210,搜「movieIds」)。TMDB 接口与 5 个示例电影 ID 照原书。 ↩

  8. 出处:同章第 235-268 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:235,搜「fetchMoviesSequentially」;:251,搜「fetchMoviesInParallel」)。串行 for...of 与并行 map+all 两版照原书。 ↩

  9. 出处:同章第 270-288 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:272,搜「Sequential Fetching」;:274,搜「Parallel Fetching」)。两种编排的用途说明照原书。 ↩

  10. 出处:「Chapter 10: Hands-On Mini Projects」第 235-249 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:235,搜「fetchMoviesSequentially」)。for...of 逐圈取数照原书。 ↩

  11. 出处:「Chapter 10: Hands-On Mini Projects」第 237-265 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:237,搜「console.time」;:253,搜「console.time」)。两处的计时标记照原书。 ↩

  12. 出处:「Chapter 10: Hands-On Mini Projects」第 363-374 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:364,搜「50%」;:366,搜「randomDelay」)。模拟接口的随机延迟、失败率与成功返回值照原书。 ↩

  13. 出处:同章第 290-374 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:364,搜「50%」)。模拟接口 50% 失败率、随机 500-1500ms 延迟照原书。 ↩

  14. 出处:同章第 419-429 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:421,搜「browser console」)。测试时控制台可见重试日志与剩余次数照原书。 ↩

  15. 出处:「Chapter 10: Hands-On Mini Projects」第 13-129 段与 431-443 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:13,搜「Setting Up」;:433,搜「Conclusion」)。"Setting Up → Implementing → Testing"的三段流程与"Feel free to extend these projects"照原书。 ↩

  16. 出处:同章第 117-121 段(text/13-ch10-chapter-10-hands-on-mini-projects.txt:121,搜「separation of concerns」)。原文:"This separation of concerns (fetching data vs. displaying data) makes our code more modular and easier to maintain." ↩

On this page