从零开始学前端 | 第十八章:定时器、异步与网络请求初步

📅 2026/7/20 19:41:47 👁️ 阅读次数 📝 编程学习
从零开始学前端 | 第十八章:定时器、异步与网络请求初步

本章定位

前面几章里,你已经逐步掌握了前端交互的基础逻辑。

你已经学习了:

  1. JavaScript 是什么,代码如何运行。
  2. 变量、数据类型与运算。
  3. 条件判断与循环。
  4. 函数与代码复用。
  5. 数组与对象。
  6. DOM 操作入门。
  7. 事件机制与交互逻辑。

到这里,你已经能写出不少“有页面反馈”的代码了。

例如:

  • 点击按钮后修改文字
  • 输入内容后同步显示
  • 提交表单前做简单校验

但如果你继续往前走,很快就会遇到一个新的关键问题:

为什么有些代码不是立刻执行,而是要等一会儿?

比如:

  • 为什么两秒后才弹出提示?
  • 为什么倒计时会一秒一秒更新?
  • 为什么请求服务器数据时,页面要先显示“加载中”?
  • 为什么fetch写在上面,结果却不是马上拿到?

这些问题,背后其实都指向同一个核心主题:

定时器、异步与网络请求

这一章要解决的核心问题是:

代码为什么不会永远按“写一行、立刻执行完一行”的方式工作,以及页面怎样在等待过程中保持合理反馈。

本章学习目标

学完这一章后,你应该能做到:

  1. 理解什么是同步,什么是异步。
  2. 理解为什么“代码不会按你想象的顺序立刻执行完”。
  3. 掌握setTimeoutsetInterval的基础用法。
  4. 理解为什么clearInterval对重复任务很重要。
  5. 理解网络请求为什么天然带有等待过程。
  6. fetch建立基础认知,知道它是怎样从服务器获取数据的。
  7. 学会在页面里处理三种基础状态:加载中、成功、失败。
  8. 能完成倒计时按钮、获取公开接口数据并渲染到页面这类基础练习。

一、为什么这一章很关键

在前面的章节里,你写的大部分代码都比较像这样:

consttotalPrice=99*2;console.log(totalPrice);

这种代码通常有一个特点:

写到哪里,基本就立刻执行到哪里。

这类执行方式很适合入门,因为它简单、直接、容易观察。

但真实前端开发里,并不是所有事情都能立刻得到结果。

例如:

  • 你想让一段代码 2 秒后再执行
  • 你想每隔 1 秒更新一次数字
  • 你想从远程服务器拿一组数据

这些事情都带有“等待”的成分。

也就是说,从这一章开始,你会第一次真正接触:

程序不是所有逻辑都同时立刻完成,有些任务需要先等一会儿。

这也是为什么这一章非常关键。

二、先理解什么是同步

同步可以先用一句最容易理解的话来记:

前一件事没做完,后一件事通常就接着排在后面做。

1. 一个最基础的同步例子

console.log("第一步:开始学习");console.log("第二步:打开编辑器");console.log("第三步:写代码");

这三行代码通常会按顺序输出:

第一步:开始学习 第二步:打开编辑器 第三步:写代码

这就是最直观的同步感觉:

一步接一步,按照当前顺序执行。

2. 为什么前面大多数基础代码都更像同步

因为前面你做的很多事情,本质上都是当前就能马上完成的:

  • 计算数字
  • 比较条件
  • 改一个变量
  • 修改页面里的某段文本

这类事情不需要额外等待。

三、再理解什么是异步

异步可以先用一句更直白的话来记:

某个任务现在先发出去,但结果要稍后才回来。

例如:

  • 等两秒后再执行
  • 每隔一秒执行一次
  • 向服务器发请求,等服务器返回数据

这些任务都不属于“立刻就能完成”的那一类。

1. 一个最简单的异步例子

console.log("第一行:开始执行");setTimeout(function(){console.log("第三行:这是两秒后才执行的内容");},2000);console.log("第二行:这行会先执行");

输出顺序通常会是:

第一行:开始执行 第二行:这行会先执行 第三行:这是两秒后才执行的内容

2. 为什么会出现这种顺序

因为:

  1. console.log("第一行...")立刻执行
  2. setTimeout(...)不是让里面的代码立刻执行,而是先告诉浏览器:
    “请在 2000 毫秒后执行这段函数”
  3. 所以后面的第二行会先继续执行
  4. 等时间到了,浏览器才回来执行定时器里的代码

3. 这是不是说明异步就是“乱序执行”

不是。

这点非常重要。

异步不是乱,而是:

有些任务需要等待,所以它们不会在当前这一刻立刻完成。

你可以先把它理解成:

同步代码是“现在就做”,异步代码更像“先登记,等条件到了再回来做”。

四、什么是定时器

前端里最容易帮助你理解异步的工具之一,就是定时器。

你可以先把定时器理解成:

让一段代码延迟执行,或者按间隔重复执行的工具。

当前阶段,最值得优先掌握的两个定时器是:

  1. setTimeout
  2. setInterval

五、setTimeout:延迟执行一次

setTimeout最适合这种场景:

我想让一段代码在一段时间后执行一次。

1. 基本写法

setTimeout(function(){要延迟执行的代码;},延迟时间);

2. 延迟时间的单位是什么

这里的时间单位不是“秒”,而是:

毫秒

例如:

  • 1000表示 1 秒
  • 2000表示 2 秒
  • 5000表示 5 秒

3. 一个最简单的例子

setTimeout(function(){console.log("两秒后,我才会出现");},2000);

4.setTimeout最适合什么场景

例如:

  • 延迟显示一条提示
  • 延迟关闭一个提示框
  • 若干秒后自动执行某段逻辑

5. 当前阶段先不用把它想复杂

你现在只要先记住:

setTimeout是“过一会儿只执行一次”。

六、setInterval:按固定间隔重复执行

如果说setTimeout是“稍后执行一次”,那setInterval更像:

每隔一段时间,就重复执行一次。

1. 基本写法

setInterval(function(){要重复执行的代码;},间隔时间);

2. 一个最简单的例子

setInterval(function(){console.log("我会每隔一秒执行一次");},1000);

3.setInterval最适合什么场景

例如:

  • 倒计时
  • 轮播计时
  • 每隔几秒更新一次显示

4. 为什么setInterval不能只会开,不会停

这点特别值得初学者尽早注意。

因为setInterval一旦开始,就会持续重复执行。

如果你不主动停止它,它就会一直继续下去。

所以在很多真实场景里,我们都需要知道怎样停止它。

七、clearInterval为什么重要

要停止一个setInterval,通常要先拿到它返回的定时器标识。

1. 一个基础例子

consttimerId=setInterval(function(){console.log("我每秒执行一次");},1000);clearInterval(timerId);

这里的意思是:

  1. 先启动一个重复定时器
  2. 浏览器返回一个定时器标识
  3. 通过clearInterval(timerId)把它停掉

2. 为什么初学阶段必须建立这个意识

因为很多人一学到setInterval,就只记得“它会重复执行”,却忘了:

重复执行通常意味着你要想清楚什么时候结束。

这和你前面学循环时的思路其实很像:

任何重复任务,都要先想清楚什么时候停。

八、setTimeoutsetInterval怎么选

这也是初学者很常见的问题。

你现在可以先用一个非常直观的判断法。

1. 如果你只是想稍后执行一次

优先考虑:

setTimeout

2. 如果你想固定间隔重复执行

优先考虑:

setInterval

3. 一个非常口语化的记法

你可以先这样记:

  • setTimeout:等一下,做一次
  • setInterval:每隔一会儿,重复做

九、一个完整示例:倒计时按钮

下面我们来做一个非常适合纯小白入门的完整例子。

目标是:

点击按钮后,按钮进入倒计时状态,5 秒后恢复可点击。

这类交互很像“获取验证码”按钮的基础原型。

1. 示例代码

<buttonid="countdown-button">获取验证码</button><script>constcountdownButton=document.getElementById("countdown-button");countdownButton.addEventListener("click",function(){letseconds=5;// 点击后先禁用按钮,避免在倒计时期间重复点击countdownButton.disabled=true;countdownButton.textContent=`${seconds}秒后重试`;consttimerId=setInterval(function(){seconds-=1;if(seconds>0){countdownButton.textContent=`${seconds}秒后重试`;return;}// 倒计时结束后,记得停止定时器并恢复按钮状态clearInterval(timerId);countdownButton.disabled=false;countdownButton.textContent="重新获取验证码";},1000);});</script>

2. 这段代码在做什么

你可以按下面顺序理解:

1. 用户点击按钮

点击事件触发后,开始执行倒计时逻辑。

2. 按钮先被禁用
countdownButton.disabled=true;

这样可以避免用户在倒计时期间不停重复点。

3. 每隔一秒更新一次文字
consttimerId=setInterval(function(){...},1000);
4. 倒计时结束后停止重复执行
clearInterval(timerId);
5. 最后恢复按钮状态

按钮重新可以点击,文字也恢复正常。

3. 这个例子为什么很重要

因为它第一次把下面这些知识真正串起来了:

  1. 事件监听
  2. 定时器
  3. 条件判断
  4. 更新 DOM
  5. 结束重复任务

这已经很接近真实前端页面中的基础交互逻辑了。

十、为什么网络请求天然是异步的

学完定时器之后,我们来进入另一类非常重要的异步任务:

网络请求

1. 什么是网络请求

你可以先把网络请求理解成:

浏览器向服务器发出请求,想要拿到某些数据。

例如:

  • 请求一组用户数据
  • 请求一篇文章详情
  • 请求商品列表

2. 为什么它不可能像普通变量那样立刻拿到

因为网络请求要经历很多步骤:

  1. 浏览器把请求发出去
  2. 服务器接收到请求
  3. 服务器准备数据
  4. 数据再通过网络返回浏览器

这中间天然就需要时间。

所以浏览器不可能像读取本地变量一样,立刻就拿到远程数据。

3. 这也是为什么网络请求一定带有“等待”

也就是说,网络请求几乎天然就是异步场景。

所以写网络请求代码时,你一定要习惯一种思路:

先发请求,再等待结果回来,然后再处理结果。

十一、什么是fetch

在现代浏览器里,请求网络数据最常见的基础方式之一就是:

fetch

你现在可以先把它理解成:

一个用来向服务器发请求并获取数据的工具。

1. 一个最简单的写法

fetch("https://jsonplaceholder.typicode.com/todos/1");

这行代码表示:

向这个地址请求数据。

2. 但只写这一行还不够

因为请求发出去以后,你还需要处理结果。

例如:

  • 请求成功后要展示什么
  • 请求失败后要提示什么
  • 等待过程中页面显示什么

这就是为什么单纯会写fetch(url)还不够,后面还要接着学“结果回来之后怎么处理”。

十二、初步理解fetch的处理流程

当前阶段,你可以先把fetch的基础流程理解成下面这几步:

  1. 发出请求
  2. 等服务器响应
  3. 把响应数据转成 JavaScript 能使用的格式
  4. 更新页面
  5. 如果失败,就显示失败提示

1. 为什么这里会出现“加载中”

因为从“请求发出去”到“结果回来”之间,通常不是瞬间完成的。

这段等待时间里,页面如果什么都不显示,用户往往会很迷惑。

所以真实页面里,通常会明确处理三种状态:

  1. 加载中
  2. 成功
  3. 失败

2. 这是前端异步阶段非常重要的一种思维

你从这一章开始,就要逐步建立这个意识:

异步任务不仅要关心“最终结果”,还要关心“等待过程中的状态变化”。

十三、then可以先怎么理解

你在fetch示例里,很快就会看到一种写法:

.then(...)

当前阶段你可以先把它理解成:

“当上一步的结果回来之后,再继续做这一步”。

比如:

fetch(url).then(function(response){...});

它大致可以先理解成:

当服务器响应回来后,再执行这个函数。

1. 当前阶段先不要被术语绊住

你以后可能会听到:

  • Promise
  • 链式调用

但现在你不需要一下子把这些底层机制全学完。

你只要先记住:

then的核心感觉就是“等结果回来后,再继续做下一步”。

十四、为什么还要写response.json()

服务器返回的数据,很多时候是 JSON 格式。

而 JavaScript 想更方便地使用这些数据,通常需要先做一步转换:

response.json()

1. 当前阶段你可以先这样理解它

把服务器返回的 JSON 数据,转成 JavaScript 更容易处理的数据。

2. 这一步之后通常就能拿到真正的数据内容

例如:

  • 数组
  • 对象

这也就和你前面刚学过的“数组与对象”真正连起来了。

十五、一个完整示例:获取公开接口数据并渲染到页面

下面我们来做一个非常典型的异步入门例子。

目标是:

  1. 点击按钮后发送网络请求
  2. 从公开接口获取一组用户数据
  3. 在页面上显示“加载中”
  4. 请求成功后把数据渲染成列表
  5. 请求失败时给出错误提示

1. 示例代码

<buttonid="load-users-button">加载用户列表</button><pid="load-status"></p><ulid="user-list"></ul><script>constloadUsersButton=document.getElementById("load-users-button");constloadStatus=document.getElementById("load-status");constuserList=document.getElementById("user-list");loadUsersButton.addEventListener("click",function(){// 发请求前先告诉用户:现在正在加载loadStatus.textContent="加载中,请稍等...";userList.textContent="";fetch("https://jsonplaceholder.typicode.com/users").then(function(response){// 如果服务器返回的不是成功状态,就主动进入失败处理if(!response.ok){thrownewError("请求失败");}// 把返回的 JSON 数据转成 JavaScript 可直接使用的数组returnresponse.json();}).then(function(users){loadStatus.textContent="加载成功";for(letindex=0;index<users.length;index+=1){constcurrentUser=users[index];constnewItem=document.createElement("li");// 把每个用户的名称和邮箱显示到页面列表中newItem.textContent=`${currentUser.name}-${currentUser.email}`;userList.appendChild(newItem);}}).catch(function(){loadStatus.textContent="加载失败,请稍后再试";});});</script>

2. 这段代码在做什么

你可以按下面顺序理解:

1. 用户点击“加载用户列表”按钮

点击事件触发后,开始执行请求逻辑。

2. 页面先进入“加载中”状态
loadStatus.textContent="加载中,请稍等...";

这样用户就知道,页面不是没反应,而是在等待结果。

3.fetch向服务器请求用户数据
fetch("https://jsonplaceholder.typicode.com/users")
4. 请求回来后,把响应转成真正的用户数组
returnresponse.json();
5. 拿到数组后,用循环把每个用户渲染成列表项
for(letindex=0;index<users.length;index+=1){...}
6. 如果失败,就显示错误提示
loadStatus.textContent="加载失败,请稍后再试";

3. 这个例子为什么很重要

因为它第一次把下面这些知识真正串起来了:

  1. 事件监听
  2. 异步请求
  3. 加载中状态
  4. 成功状态
  5. 失败状态
  6. 数组循环
  7. DOM 渲染

这已经非常接近真实前端页面的数据加载流程了。

十六、为什么fetch后面立刻拿数据,经常拿不到

这是初学者在异步阶段最经典的困惑之一。

例如你可能会写出:

letusers=[];fetch("https://jsonplaceholder.typicode.com/users").then(function(response){returnresponse.json();}).then(function(data){users=data;});console.log(users);

很多初学者会期待控制台直接打印出完整用户数组。

但现实里,这一行:

console.log(users);

很可能先输出的还是:

[]

1. 为什么会这样

因为:

  1. 请求发出去以后,结果还没回来
  2. 后面的console.log(users)已经先执行了
  3. 真正给users赋值的代码,是在异步结果回来后才执行的

2. 这一点说明了什么

说明网络请求场景里,你必须慢慢习惯一种思维:

不是你“写在后面”的代码就一定“最后才拿到结果”,关键是它是不是在等待异步结果。

3. 当前阶段一个很实用的原则

如果某段代码依赖异步拿到的数据,那它通常也应该写在:

  • then(...)里面
  • 或者后续专门处理结果的位置里

这会是你后面学习异步最重要的一步。

十七、为什么“加载中、成功、失败”特别重要

很多纯小白一开始写请求逻辑时,只关心:

我最后有没有拿到数据。

但真实页面里,用户实际感受到的往往不只是“有没有结果”,还包括:

  1. 点击后有没有反应
  2. 现在是不是在加载
  3. 失败了有没有提示

所以从这一章开始,你要慢慢建立一个非常重要的前端意识:

一个异步请求,至少要考虑加载中、成功、失败三种状态。

1. 加载中

告诉用户:

页面已经开始工作了,请稍等。

2. 成功

把结果展示给用户看。

3. 失败

明确告诉用户出了问题,而不是让页面看起来像什么都没发生。

这套状态思维,后面在 React、Next.js 里会越来越重要。

十八、这一章和前面学过的知识是怎么连起来的

第十八章不是孤立的新内容,而是在把你前面学过的所有基础逐渐接到“等待”和“外部数据”上。

1. 事件会继续出现

很多异步任务都不是页面一加载就做,而是:

  • 点击按钮后开始倒计时
  • 点击按钮后发请求

2. 条件判断会继续出现

例如:

  • 倒计时是否结束
  • 请求是否成功
  • 输入是否为空

3. DOM 操作会继续出现

例如:

  • 更新倒计时文字
  • 显示加载中提示
  • 把返回数据渲染到页面上

4. 数组与对象也会继续出现

网络请求返回的数据,很多时候本质上就是:

  • 一个对象
  • 一组对象数组

所以你前面学的那些基础,都会在这里真正串起来。

十九、初学者最容易踩的坑

这一章的坑非常典型,而且很多人会在这里第一次明显感受到“代码时机”和“状态变化”的难度。

1. 以为1000是 1000 秒

再强调一次:

定时器的时间单位是毫秒,不是秒。

所以:

  • 1000是 1 秒
  • 2000是 2 秒

2.setInterval开了之后忘记停

这会导致重复任务一直继续执行。

所以每次写setInterval时,都可以先问自己:

它什么时候结束?

3. 以为异步代码会自动“等一等”

很多初学者会下意识觉得:

我都已经写在下面了,那它应该等上面的请求回来再继续吧?

但异步不是这样工作的。

如果你的代码不在正确的异步处理位置里,它就很可能先执行。

4. 请求时只关心成功,不处理加载和失败

这会让页面很容易出现一种尴尬情况:

  • 点击了
  • 页面没反应
  • 用户不知道是卡住了,还是失败了

所以从现在开始,要慢慢养成状态意识。

5. 只创建了列表项,却忘了插入页面

在请求成功渲染列表时,这种错误也很常见。

例如你只写了:

constnewItem=document.createElement("li");newItem.textContent="用户信息";

但没有写:

userList.appendChild(newItem);

那用户自然还是看不到内容。

6. 一上来就做太复杂的请求联动

当前阶段更推荐你先练熟下面这些小任务:

  1. 延迟执行一次
  2. 每秒更新一次
  3. 请求一组公开数据
  4. 显示加载中、成功、失败

这几类练熟后,再去做更复杂的异步交互会稳很多。

二十、本章实践练习

这一章最适合做“有等待、有反馈”的练习。

重点不是页面有多花哨,而是让你真正体会:

异步任务的核心,不只是拿到结果,而是正确处理等待过程。

1. 练习 1:写一个倒计时按钮

请创建一个按钮,点击后:

  1. 按钮禁用
  2. 文本显示“5 秒后重试”
  3. 每秒减一
  4. 倒计时结束后恢复按钮

这个练习重点是:

  1. setInterval
  2. clearInterval
  3. DOM 更新

2. 练习 2:写一个延迟提示

请创建一个按钮,点击后不要立刻显示结果,而是:

  • 2 秒后显示“操作完成”

这个练习重点是:

  1. setTimeout
  2. 理解“稍后执行一次”

3. 练习 3:从公开接口获取一组数据并渲染到页面

你可以继续使用:

https://jsonplaceholder.typicode.com/users

目标是:

  1. 点击按钮后发请求
  2. 页面显示“加载中”
  3. 请求成功后把用户列表显示出来
  4. 请求失败时显示“加载失败”

这个练习重点是:

  1. fetch
  2. then
  3. DOM 渲染
  4. 加载中 / 成功 / 失败三种状态

4. 挑战练习:请求数据后只显示部分内容

如果你想继续往前多走一点,可以试试这个挑战:

请求用户列表后,不是把所有字段都展示出来,而是只显示:

  • 用户名
  • 邮箱

或者只显示前 3 个用户。

这个练习会帮助你把:

  • 数组
  • 对象
  • 循环
  • DOM
  • 异步请求

真正串起来。

二十一、学习重点提示

这一章最重要的,不是背下所有 API,而是先真正建立“等待”和“状态”的思维。

请重点记住:

  1. 同步代码更像当前就能完成的任务,异步代码通常需要等待结果。
  2. setTimeout是延迟执行一次,setInterval是按固定间隔重复执行。
  3. 任何重复任务,都要尽早想清楚什么时候停。
  4. 网络请求天然带有等待过程,所以它本质上就是异步场景。
  5. fetch的核心流程可以先理解为:发请求 -> 等响应 -> 转数据 -> 更新页面。
  6. 异步阶段尤其要重视三种状态:加载中、成功、失败。
  7. 如果代码依赖异步结果,那它通常不能写在异步处理逻辑外面直接假设已经拿到数据。

如果你只记一句话,请记住:

异步的核心,不是“代码变复杂了”,而是“你必须开始学会处理等待,以及等待过程中的状态变化”。

二十二、本章小结

这一章,你已经从“用户操作触发页面变化”进一步走到了“页面开始处理等待、处理时间、处理远程数据”。

你已经学习了:

  • 什么是同步
  • 什么是异步
  • setTimeoutsetInterval的基础用法
  • 为什么clearInterval很重要
  • 为什么网络请求天然是异步的
  • fetch的基础认知和基本处理流程
  • 为什么异步阶段要重视加载中、成功、失败三种状态
  • 如何完成倒计时按钮和获取用户列表这类基础练习

这一步非常关键。

因为从这一章开始,你写的前端页面已经不只是“立刻改一下页面”,而是开始接近真实项目里的数据加载和用户等待场景。

后面当你继续学习 ES6+ 语法、React、Next.js 时,这种“异步 + 状态”的思维会越来越重要。

二十三、课后思考题

请你思考下面几个问题:

  1. 为什么说异步不是“乱序执行”,而是“有些任务需要等待”?
  2. setTimeoutsetInterval最直观的区别是什么?
  3. 为什么写setInterval时,一定要提前考虑“什么时候停”?
  4. 为什么网络请求几乎天然就是异步场景?
  5. 为什么说一个请求除了成功结果,还要考虑加载中和失败状态?
  6. 为什么很多初学者会在fetch后面立刻使用数据时踩坑?

建议你用自己的话把答案写下来。只要你能把这些问题说明白,说明你已经真正开始建立异步思维了。

二十四、下一章预告

下一章我们会继续学习:

从零开始学前端 | 第十九章:ES6+ 常用语法

到那时,你会开始接触模板字符串、解构赋值、展开运算符、箭头函数等更现代的 JavaScript 写法,也会逐渐看懂越来越多真实项目里的代码。