从零开始学前端 | 第十八章:定时器、异步与网络请求初步
本章定位
前面几章里,你已经逐步掌握了前端交互的基础逻辑。
你已经学习了:
- JavaScript 是什么,代码如何运行。
- 变量、数据类型与运算。
- 条件判断与循环。
- 函数与代码复用。
- 数组与对象。
- DOM 操作入门。
- 事件机制与交互逻辑。
到这里,你已经能写出不少“有页面反馈”的代码了。
例如:
- 点击按钮后修改文字
- 输入内容后同步显示
- 提交表单前做简单校验
但如果你继续往前走,很快就会遇到一个新的关键问题:
为什么有些代码不是立刻执行,而是要等一会儿?
比如:
- 为什么两秒后才弹出提示?
- 为什么倒计时会一秒一秒更新?
- 为什么请求服务器数据时,页面要先显示“加载中”?
- 为什么
fetch写在上面,结果却不是马上拿到?
这些问题,背后其实都指向同一个核心主题:
定时器、异步与网络请求
这一章要解决的核心问题是:
代码为什么不会永远按“写一行、立刻执行完一行”的方式工作,以及页面怎样在等待过程中保持合理反馈。
本章学习目标
学完这一章后,你应该能做到:
- 理解什么是同步,什么是异步。
- 理解为什么“代码不会按你想象的顺序立刻执行完”。
- 掌握
setTimeout和setInterval的基础用法。 - 理解为什么
clearInterval对重复任务很重要。 - 理解网络请求为什么天然带有等待过程。
- 对
fetch建立基础认知,知道它是怎样从服务器获取数据的。 - 学会在页面里处理三种基础状态:加载中、成功、失败。
- 能完成倒计时按钮、获取公开接口数据并渲染到页面这类基础练习。
一、为什么这一章很关键
在前面的章节里,你写的大部分代码都比较像这样:
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. 为什么会出现这种顺序
因为:
console.log("第一行...")立刻执行setTimeout(...)不是让里面的代码立刻执行,而是先告诉浏览器:
“请在 2000 毫秒后执行这段函数”- 所以后面的第二行会先继续执行
- 等时间到了,浏览器才回来执行定时器里的代码
3. 这是不是说明异步就是“乱序执行”
不是。
这点非常重要。
异步不是乱,而是:
有些任务需要等待,所以它们不会在当前这一刻立刻完成。
你可以先把它理解成:
同步代码是“现在就做”,异步代码更像“先登记,等条件到了再回来做”。
四、什么是定时器
前端里最容易帮助你理解异步的工具之一,就是定时器。
你可以先把定时器理解成:
让一段代码延迟执行,或者按间隔重复执行的工具。
当前阶段,最值得优先掌握的两个定时器是:
setTimeoutsetInterval
五、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);这里的意思是:
- 先启动一个重复定时器
- 浏览器返回一个定时器标识
- 通过
clearInterval(timerId)把它停掉
2. 为什么初学阶段必须建立这个意识
因为很多人一学到setInterval,就只记得“它会重复执行”,却忘了:
重复执行通常意味着你要想清楚什么时候结束。
这和你前面学循环时的思路其实很像:
任何重复任务,都要先想清楚什么时候停。
八、setTimeout和setInterval怎么选
这也是初学者很常见的问题。
你现在可以先用一个非常直观的判断法。
1. 如果你只是想稍后执行一次
优先考虑:
setTimeout2. 如果你想固定间隔重复执行
优先考虑:
setInterval3. 一个非常口语化的记法
你可以先这样记:
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. 这个例子为什么很重要
因为它第一次把下面这些知识真正串起来了:
- 事件监听
- 定时器
- 条件判断
- 更新 DOM
- 结束重复任务
这已经很接近真实前端页面中的基础交互逻辑了。
十、为什么网络请求天然是异步的
学完定时器之后,我们来进入另一类非常重要的异步任务:
网络请求
1. 什么是网络请求
你可以先把网络请求理解成:
浏览器向服务器发出请求,想要拿到某些数据。
例如:
- 请求一组用户数据
- 请求一篇文章详情
- 请求商品列表
2. 为什么它不可能像普通变量那样立刻拿到
因为网络请求要经历很多步骤:
- 浏览器把请求发出去
- 服务器接收到请求
- 服务器准备数据
- 数据再通过网络返回浏览器
这中间天然就需要时间。
所以浏览器不可能像读取本地变量一样,立刻就拿到远程数据。
3. 这也是为什么网络请求一定带有“等待”
也就是说,网络请求几乎天然就是异步场景。
所以写网络请求代码时,你一定要习惯一种思路:
先发请求,再等待结果回来,然后再处理结果。
十一、什么是fetch
在现代浏览器里,请求网络数据最常见的基础方式之一就是:
fetch你现在可以先把它理解成:
一个用来向服务器发请求并获取数据的工具。
1. 一个最简单的写法
fetch("https://jsonplaceholder.typicode.com/todos/1");这行代码表示:
向这个地址请求数据。
2. 但只写这一行还不够
因为请求发出去以后,你还需要处理结果。
例如:
- 请求成功后要展示什么
- 请求失败后要提示什么
- 等待过程中页面显示什么
这就是为什么单纯会写fetch(url)还不够,后面还要接着学“结果回来之后怎么处理”。
十二、初步理解fetch的处理流程
当前阶段,你可以先把fetch的基础流程理解成下面这几步:
- 发出请求
- 等服务器响应
- 把响应数据转成 JavaScript 能使用的格式
- 更新页面
- 如果失败,就显示失败提示
1. 为什么这里会出现“加载中”
因为从“请求发出去”到“结果回来”之间,通常不是瞬间完成的。
这段等待时间里,页面如果什么都不显示,用户往往会很迷惑。
所以真实页面里,通常会明确处理三种状态:
- 加载中
- 成功
- 失败
2. 这是前端异步阶段非常重要的一种思维
你从这一章开始,就要逐步建立这个意识:
异步任务不仅要关心“最终结果”,还要关心“等待过程中的状态变化”。
十三、then可以先怎么理解
你在fetch示例里,很快就会看到一种写法:
.then(...)当前阶段你可以先把它理解成:
“当上一步的结果回来之后,再继续做这一步”。
比如:
fetch(url).then(function(response){...});它大致可以先理解成:
当服务器响应回来后,再执行这个函数。
1. 当前阶段先不要被术语绊住
你以后可能会听到:
- Promise
- 链式调用
但现在你不需要一下子把这些底层机制全学完。
你只要先记住:
then的核心感觉就是“等结果回来后,再继续做下一步”。
十四、为什么还要写response.json()
服务器返回的数据,很多时候是 JSON 格式。
而 JavaScript 想更方便地使用这些数据,通常需要先做一步转换:
response.json()1. 当前阶段你可以先这样理解它
把服务器返回的 JSON 数据,转成 JavaScript 更容易处理的数据。
2. 这一步之后通常就能拿到真正的数据内容
例如:
- 数组
- 对象
这也就和你前面刚学过的“数组与对象”真正连起来了。
十五、一个完整示例:获取公开接口数据并渲染到页面
下面我们来做一个非常典型的异步入门例子。
目标是:
- 点击按钮后发送网络请求
- 从公开接口获取一组用户数据
- 在页面上显示“加载中”
- 请求成功后把数据渲染成列表
- 请求失败时给出错误提示
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. 这个例子为什么很重要
因为它第一次把下面这些知识真正串起来了:
- 事件监听
- 异步请求
- 加载中状态
- 成功状态
- 失败状态
- 数组循环
- 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. 为什么会这样
因为:
- 请求发出去以后,结果还没回来
- 后面的
console.log(users)已经先执行了 - 真正给
users赋值的代码,是在异步结果回来后才执行的
2. 这一点说明了什么
说明网络请求场景里,你必须慢慢习惯一种思维:
不是你“写在后面”的代码就一定“最后才拿到结果”,关键是它是不是在等待异步结果。
3. 当前阶段一个很实用的原则
如果某段代码依赖异步拿到的数据,那它通常也应该写在:
then(...)里面- 或者后续专门处理结果的位置里
这会是你后面学习异步最重要的一步。
十七、为什么“加载中、成功、失败”特别重要
很多纯小白一开始写请求逻辑时,只关心:
我最后有没有拿到数据。
但真实页面里,用户实际感受到的往往不只是“有没有结果”,还包括:
- 点击后有没有反应
- 现在是不是在加载
- 失败了有没有提示
所以从这一章开始,你要慢慢建立一个非常重要的前端意识:
一个异步请求,至少要考虑加载中、成功、失败三种状态。
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. 练习 1:写一个倒计时按钮
请创建一个按钮,点击后:
- 按钮禁用
- 文本显示“5 秒后重试”
- 每秒减一
- 倒计时结束后恢复按钮
这个练习重点是:
setIntervalclearInterval- DOM 更新
2. 练习 2:写一个延迟提示
请创建一个按钮,点击后不要立刻显示结果,而是:
- 2 秒后显示“操作完成”
这个练习重点是:
setTimeout- 理解“稍后执行一次”
3. 练习 3:从公开接口获取一组数据并渲染到页面
你可以继续使用:
https://jsonplaceholder.typicode.com/users目标是:
- 点击按钮后发请求
- 页面显示“加载中”
- 请求成功后把用户列表显示出来
- 请求失败时显示“加载失败”
这个练习重点是:
fetchthen- DOM 渲染
- 加载中 / 成功 / 失败三种状态
4. 挑战练习:请求数据后只显示部分内容
如果你想继续往前多走一点,可以试试这个挑战:
请求用户列表后,不是把所有字段都展示出来,而是只显示:
- 用户名
- 邮箱
或者只显示前 3 个用户。
这个练习会帮助你把:
- 数组
- 对象
- 循环
- DOM
- 异步请求
真正串起来。
二十一、学习重点提示
这一章最重要的,不是背下所有 API,而是先真正建立“等待”和“状态”的思维。
请重点记住:
- 同步代码更像当前就能完成的任务,异步代码通常需要等待结果。
setTimeout是延迟执行一次,setInterval是按固定间隔重复执行。- 任何重复任务,都要尽早想清楚什么时候停。
- 网络请求天然带有等待过程,所以它本质上就是异步场景。
fetch的核心流程可以先理解为:发请求 -> 等响应 -> 转数据 -> 更新页面。- 异步阶段尤其要重视三种状态:加载中、成功、失败。
- 如果代码依赖异步结果,那它通常不能写在异步处理逻辑外面直接假设已经拿到数据。
如果你只记一句话,请记住:
异步的核心,不是“代码变复杂了”,而是“你必须开始学会处理等待,以及等待过程中的状态变化”。
二十二、本章小结
这一章,你已经从“用户操作触发页面变化”进一步走到了“页面开始处理等待、处理时间、处理远程数据”。
你已经学习了:
- 什么是同步
- 什么是异步
setTimeout和setInterval的基础用法- 为什么
clearInterval很重要 - 为什么网络请求天然是异步的
fetch的基础认知和基本处理流程- 为什么异步阶段要重视加载中、成功、失败三种状态
- 如何完成倒计时按钮和获取用户列表这类基础练习
这一步非常关键。
因为从这一章开始,你写的前端页面已经不只是“立刻改一下页面”,而是开始接近真实项目里的数据加载和用户等待场景。
后面当你继续学习 ES6+ 语法、React、Next.js 时,这种“异步 + 状态”的思维会越来越重要。
二十三、课后思考题
请你思考下面几个问题:
- 为什么说异步不是“乱序执行”,而是“有些任务需要等待”?
setTimeout和setInterval最直观的区别是什么?- 为什么写
setInterval时,一定要提前考虑“什么时候停”? - 为什么网络请求几乎天然就是异步场景?
- 为什么说一个请求除了成功结果,还要考虑加载中和失败状态?
- 为什么很多初学者会在
fetch后面立刻使用数据时踩坑?
建议你用自己的话把答案写下来。只要你能把这些问题说明白,说明你已经真正开始建立异步思维了。
二十四、下一章预告
下一章我们会继续学习:
从零开始学前端 | 第十九章:ES6+ 常用语法
到那时,你会开始接触模板字符串、解构赋值、展开运算符、箭头函数等更现代的 JavaScript 写法,也会逐渐看懂越来越多真实项目里的代码。