第五章:异步之门,Promise的初体验
转眼过了几天,苏澈依旧沉迷于补充 JavaScript 的底层知识。相比起重生前的焦躁与被动,如今的他,心中多了一份笃定。他知道,只要夯实基础,接下来的路才能走得更远。
这天下午,校园里忽然热闹起来:学校官方公众号宣布,将举办一场“校园编程马拉松(Hackathon)”,为期两天一夜,主题是“校园生活便捷化”。
“据说有很多老师和企业代表会来观摩呢,表现好的小组还有机会拿到校外的实习或投资。”赵大海刷着论坛,一脸兴奋,“澈子,咱们要不去凑凑热闹?”
“好啊!”苏澈眼睛一亮。前世的他也听说过这次 Hackathon,却因为自己当时沉迷游戏而错过。既然重生,那这回可不能放过。
林雨菲想了想,也点头:“我也参加。可以练练手,一边把学的东西实际用上。”
三人一拍即合,立刻报名参赛。并在报名表里写下:队名:OverTime——赵大海的创意,“加班人生,何其悲壮”。
黑客松开场前一晚,三人在宿舍里商量。“要做啥项目才能在两天内快速出成果,又能让评委眼前一亮?”
“做一个校园活动信息平台如何?”林雨菲提议,“每次学校组织活动,通知渠道都挺分散,有些人错过报名,有些人无法及时知道结果。”
赵大海连连点头:“好啊,咱们就搞个单页应用(SPA),把所有活动汇总起来,用户还能在线报名。”
“那就需要处理数据请求,比如获取活动列表、提交报名表单,这些可离不开异步。”苏澈笑道,“刚好可以讲讲 Promise、async/await,还有事件循环给你们听。”
就在此时,宿舍门突然被敲响。
“谁啊?”
门外探进一个脑袋——一个染着一点黄毛的男生,名叫黄旭,他身后还跟着两名同学,神情颇为傲慢。
“哟,你们也是来参加 Hackathon 的?我们是 ByteStorm 队。”
赵大海皱眉:“啥事?”
黄旭环顾宿舍,目光落在苏澈身上:“听说你们也在搞前端项目?我们可是号称全栈精英队,用 Node.js + Angular 做前后端,再配个 MongoDB 做数据库,实时交互还能试试 WebSocket,虽然还是草案,但高端得很。你们可别输得太难看哦。”
说完,他和同伴相视一笑,转身扬长而去。
“切,这家伙谁啊?”赵大海一肚子不爽。
林雨菲撇撇嘴:“好像是计算机社团的红人,前后端都略懂些,性格挺狂。咱们别理他。”
苏澈倒不在意:“就是个初出茅庐的小子,咱们做好自己就行。越是这样的人,越能在比赛里激起斗志。”
三人对视一笑,更坚定了要在这次比赛里一展身手的想法。
在正式筹备 Hackathon 项目前,苏澈再一次带着室友们复习起 JavaScript 的事件循环。毕竟要处理异步请求,掌握宏任务、微任务顺序是关键。
他先在白板上画了一个示意图:
┌─────────────────────────────┐
│ 宏任务队列(Macro Task) │
└─────────────────────────────┘
│ ↑
│ │
┌─────────────┐ │ │
│ JS引擎执行栈 │ │ │
└─────────────┘ │ │
│ │
┌─────────────────────────────┐
│ 微任务队列(Micro Task) │
└─────────────────────────────┘
│
↓
( 事件循环轮询 )
“JavaScript 是单线程的,所谓事件循环(Event Loop),就是把要执行的任务分到不同的队列里,然后按照一定顺序执行。常见的分类是:宏任务(Macro Task) 和 微任务(Micro Task)。”
林雨菲点点头:“我知道 setTimeout、setInterval 之类属于宏任务。”
“对,像 I/O 操作、浏览器渲染这些,也算宏任务。相反,Promise.then()、async/await 里的 await 后续回调都属于微任务,会在当前宏任务结束后、下一次事件循环之前先执行完。”
苏澈随手写下了一个示例,演示两者的执行顺序:
console.log('开始');
setTimeout(() => {
console.log('宏任务:setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('微任务:Promise.then');
});
console.log('结束');
“执行栈会先跑同步代码,所以先打印 开始,然后 结束。接下来,微任务队列先于宏任务队列,所以 Promise.then 回调先被执行,然后才是 setTimeout。”
赵大海:“就是输出顺序:开始->结束->微任务->宏任务?”
“对。”苏澈笑道,“这点会在咱们之后的异步请求中体现得更明显。”
明确了事件循环后,苏澈又敲出了一个最基础的 Promise 示例,让大家对比回调写法的差异。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟异步请求成功
const data = { message: 'Hello from the server' };
resolve(data);
}, 1000);
});
}
fetchData()
.then(response => {
console.log('拿到数据:', response);
return '处理完数据';
})
.then(msg => {
console.log(msg);
})
.catch(error => {
console.error('出错啦:', error);
});
“用 .then() 就避免了回调层层嵌套。”
紧接着,他又展示了 async/await 写法:
async function main() {
try {
const response = await fetchData();
console.log('拿到数据:', response);
const nextMsg = '处理完数据';
console.log(nextMsg);
} catch (err) {
console.error(err);
}
}
main();
“这样就更像同步代码,可读性更好。不过它其实是在背后把操作放到微任务队列里。”
林雨菲恍然大悟:“了解了事件循环再看这些,确实更清晰。”
第二天下午,学校礼堂被改造成了比赛场地。每支队伍都忙着调试自己的项目。
“欢迎大家来到本次校园 Hackathon,希望你们能发挥创意,做出有价值的作品。”主持人是学校创新创业中心的老师,“比赛结果将由导师评审团现场打分,最高分的团队还可获得校外企业的投资邀请函。”
“啧,资本的味道。”赵大海忍不住吐槽,“咱们可别想太多,先把项目完成。”
林雨菲翻了翻资料:“先跑起项目能演示就不错了。”
苏澈已经打开编辑器,键入项目骨架。他们的作品名为“校园活动信息平台”,一个单页应用,可以展示活动列表,还能在线报名。
就在 OverTime 小队埋头苦干时,黄旭带着 ByteStorm 战队路过。
“哟,你们也是做活动平台?可别撞车啊。”黄旭漫不经心地瞥了一眼屏幕,“我们用的是 Node.js + Angular + MongoDB,甚至还想试试 WebSocket 做实时更新。你们要加油啦。”
说完,他满脸带着轻蔑,转身离开。
赵大海攥紧拳头:“什么人啊,欠收拾。”
苏澈微笑:“别理他,专心把我们自己的功能做好。”
比赛进行到第二天清晨,OverTime 小队已经做出了雏形:前端通过 fetch 加载本地 JSON,能显示活动列表,还实现了报名功能。
“现在只要对界面进行一点美化,就能正式演示了。”林雨菲擦了擦额头上的汗。
谁知此时,校方提供的临时服务器出现端口冲突,好几支队伍都连不上后端环境,场面一片混乱。
“完了,我的 Node.js 跑不起来了!” “另一个队竟然占了 3000 端口!”
赵大海也急得直跺脚:“咱们也得用 Node 来跑 JSON 吧?万一连不上怎么办?”
远处,黄旭见此情形,忍不住冷笑:“没个后端可怎么展示?只怕你们要凉咯。”
林雨菲皱眉:“要不换端口?”
苏澈脑中灵光一闪:“别抢端口了,咱们干脆直接用静态文件 + JSON,不依赖 Node,就可以用浏览器本地或任何简单服务器跑起来。”
“对啊!”赵大海一拍脑门,“那咱们就不用抢端口了。”
说干就干,不到半小时就把代码改好了。虽然功能简单,但却不受端口限制。其他队伍还在为后端而焦头烂额,他们却已经能顺利展示。
到了评审环节,苏澈和队友们向评委展示“校园活动信息平台”:
- 活动列表:前端异步加载
activities.json,渲染到页面; - 报名功能:填写名字后发起
Promise请求,模拟提交,并展示结果; - 界面:虽然简陋,但简洁直观。
“没想到用纯前端就能解决这么多问题。”一位导师点评,“而且异步逻辑处理得相当清晰,没有回调地狱,值得肯定。”
赵大海兴奋得快要跳起来。林雨菲也长舒一口气,脸上满是笑容。
不远处,黄旭盯着他们的作品一言不发,眼神里闪过一丝意外,似乎没料到 OverTime 小队能如此轻松地绕过后端限制。
“看来他们也不简单嘛。”黄旭嘀咕一句,随后转身离开。
评审结束后,OverTime 小队的得分还算可观,至少排在前列。
苏澈则微微一笑:
“Promise、async/await……还有事件循环机制,都只是开始。咱们先踏踏实实地走好每一步,就不怕任何人轻视。”
赵大海高呼:“没错,我们可不会轻易被打倒!”
林雨菲也笑了:“接下来,还有更多的技术可以学,这才只是个开端。”
在灯火辉煌的礼堂里,三人朝着远处明亮的未来举起拳头。也许下一次,他们会更坚定、更强大地站到那个聚光灯下。
第六章:新的开始,ES6的星火
Hackathon 圆满结束后,校园里关于编程与创新的热度一时没有消散。OverTime 小队虽然没能拿到第一,但他们凭借独特的前端思路和稳定的项目演示,成功跻身前三名,得到了一些老师和企业代表的青睐。
比赛结束的第二天清晨,苏澈依旧像往常一样,起得比闹钟还早。他坐在宿舍小桌子旁,一边喝着速溶咖啡,一边翻看着自己记满笔记的笔记本。Hackathon 的参赛经历让他对自己的方向更笃定:
“闭包、this、事件循环、异步编程……已经走了不少路了。但还有ES6的新特性、DOM高级操作、BOM、设计模式、以及未来的框架构想,都要逐步落实。”
他合上笔记本,心里暗想:
“我可不能再浪费时间。这一次,我要在大学时代就把那些 2025 年的经验和技术提前带到这里。”
刚走进教室,赵大海便兴冲冲地凑上来:“澈子,听说有人想找咱们聊聊项目的事?你猜是谁?”
“谁?该不会是那个黄旭吧?”苏澈一挑眉。
“不不,黄旭那小子看到咱们能用静态 JSON 做演示,已经在群里大呼‘撞大运’,估计还在闷头研究他的 Node.js 呢。”赵大海嘿嘿一笑,“是计算机系的几个老师,还有创业社团的学长,说是对咱们的项目感兴趣,想要再深入交流一下。”
苏澈点点头:“好啊,让林雨菲也一起,咱们三个人把事情说清楚。”
一旁的林雨菲刚好听到,便走过来:“既然他们感兴趣,那我们就把思路完善一下。只不过咱们目前的‘校园活动平台’还是很简陋。”
赵大海耸肩:“简陋是简陋,可毕竟是个原型嘛。反正老师们也知道这是黑客松的产物。”
林雨菲认真道:“我正想趁这个机会好好优化一下,比如加点本地存储功能,用 localStorage 或 sessionStorage 来保存用户信息,不用每次都重新输入。”
苏澈笑道:“好主意。那咱们晚上聚到实验室去?我顺便给你们讲讲 ES6 的一些新特性,能把代码写得更优雅。”
“好嘞!”赵大海一拍手掌,“那就这么定了。”
晚饭过后,三人再次聚首在项目实验室。电脑荧光的灯影下,几位同学正在埋头赶课设,气氛略显紧张。但对苏澈他们来说,这里却是自由探索的乐园。
“先从最常用的 let、const 开始吧。”苏澈在投影仪上打开一段示例代码。
// ES5 写法
var name = 'Alice';
// ES6 写法
let age = 20;
const PI = 3.14159;
“在 ES5 里,我们习惯用 var 声明变量,但 var 是函数级作用域,而且会存在变量提升等问题。ES6 引入 let、const,就变成块级作用域,更安全、更清晰。”
赵大海摩挲下巴:“我试过一次 let,但一直分不清它和 var 的区别。”
“一个最直观的区别就是:” 苏澈随手演示,
{
var a = 10;
}
console.log(a); // 10,var 是函数级作用域
{
let b = 20;
}
console.log(b); // ReferenceError: b is not defined
“用 let 声明的变量只在花括号块内可见,也就是说它是块级作用域,不会污染外部作用域。而 var 在块级并不生效,只有在函数级才会限制作用域。”
林雨菲点头:“那 const 就是用来声明常量?”
“对,const 声明的变量一旦赋值就不能再改。”苏澈转而又写了一个示例:
const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]
arr = [9, 8, 7]; // TypeError: Assignment to constant variable.
“要注意的是,const 不代表内容完全不可变,而是引用地址不可变。所以像数组、对象这种引用类型,你依然可以改它的内部值,但不能把整个引用换掉。”
赵大海“哦”了一声:“怪不得我之前试图给对象重新赋值时报错,还以为是 bug 呢。”
苏澈笑了笑:“这就是 ES6 的一大改进。除了这些,还有 解构赋值、箭头函数、模板字符串、增强的对象字面量、Symbol、Set/Map 等等,都能让咱们写代码更舒服。”
“那赶紧来点示例?”林雨菲打开笔记软件。
“比如解构赋值吧。” 苏澈在投影上敲了这样一段:
// 对象解构
const person = {
firstName: 'Alice',
lastName: 'Wonderland',
age: 18
};
// 传统写法
// const firstName = person.firstName;
// const lastName = person.lastName;
// 解构写法
const { firstName, lastName } = person;
console.log(firstName, lastName); // Alice Wonderland
// 也可以改名
const { age: userAge } = person;
console.log(userAge); // 18
// 数组解构
const arr = [10, 20, 30];
const [x, y, z] = arr;
console.log(x, y, z); // 10 20 30
林雨菲赞叹:“好方便,以前得一行行赋值。”
赵大海满眼冒星星:“太酷了吧……那箭头函数呢?我只知道可以写 () => {} 这么一行。”
“箭头函数除了简洁,还可以绑定外层 this。”苏澈演示道:
// 传统写法
var obj = {
nums: [1, 2, 3],
double: function() {
this.nums.forEach(function(item) {
console.log(item * 2);
});
}
};
obj.double(); // 有时需要bind?
// 箭头函数写法
let obj2 = {
nums: [1, 2, 3],
double() {
this.nums.forEach((item) => {
console.log(item * 2);
});
}
};
obj2.double();
“箭头函数不会创建自己的 this,它的 this 继承自外部作用域,能避免很多 bind 的麻烦。顺带一提,如果只返回一个表达式,连花括号都能省。”
赵大海赞不绝口:“这比我之前写的传统回调好看多了。”
林雨菲:“那模板字符串呢?是在字符串里写个 ${变量}?”
“对。” 苏澈敲下一行:
const name = '前端星人';
console.log(`Hello, ${name}!你的幸运数字是 ${1+2+3}.`);
// Hello, 前端星人!你的幸运数字是 6.
“用反引号``就能插入变量或表达式,方便多了。”
“好了,ES6 大致介绍到这。”苏澈收回视线,“你不是说想做个 ‘记住用户信息’ 的功能吗?可以用 localStorage 或 sessionStorage 来存储用户数据。”
林雨菲接过话头:“嗯,我打算在报名页面里,让用户输入姓名后,自动保存到 localStorage。下次再进来,就不用重复输入。”
她打开编辑器,写了几行:
// 监听输入事件
const usernameInput = document.getElementById('username');
usernameInput.addEventListener('input', () => {
// 每次输入都存储
localStorage.setItem('username', usernameInput.value);
});
// 页面加载时,如果 localStorage 有值,就填充
window.addEventListener('DOMContentLoaded', () => {
const savedName = localStorage.getItem('username');
if (savedName) {
usernameInput.value = savedName;
}
});
赵大海凑过来:“这样就行了?那 sessionStorage 区别何在?”
“sessionStorage 是会话级别的,浏览器关闭就消失;localStorage 相对持久,只要不手动清除或超出容量,就能一直存。比赛时我们只要在客户端读写就行,不用跑后端。”
“明白了,那这思路跟我们 Hackathon 用 JSON 模拟数据有异曲同工之妙,都不依赖后端。”
苏澈微笑:“对。不过真要做正式系统,还是要后端数据库的。”
“那等以后再说吧。”赵大海敲下 Enter 键,“咱们先把能做的做好。”
正当三人讨论得兴起,实验室外忽然出现一个身影。对方轻轻敲门,露出一个带着金属框眼镜的学长模样。
“打扰一下,你们是 OverTime 小队吧?我叫王雷,是学校创业社团的。”
林雨菲最先回应:“王雷学长?我好像在社团宣传里见过你。”
“哈哈,我就是跑宣传的。”王雷推了推眼镜,“我听几个老师说,你们在 Hackathon 上的项目挺有意思,想跟你们聊聊,要不要进我们社团?或者说有没有兴趣把这个项目继续做大?”
赵大海愣了愣:“做大?就这个校园活动平台?”
“嗯,校园活动信息整合,其实是个不错的领域。如果能扩展到更多高校,甚至建立一个学生社交社区,也不是不可能的方向。”王雷语气很诚恳,“我们社团里有几位师兄师姐也想凑一支创业队伍,你们愿意一起试试吗?”
苏澈和林雨菲对视一眼,心里都有些犹豫。毕竟他们目前只是想学习技术,还没想过真正“创业”。
赵大海却先开口:“我们要考虑一下吧。毕竟这项目还很粗糙,能不能真的落地都是问题。”
王雷笑了笑:“我理解,创业也不是一蹴而就。你们先考虑,不急。”
他留下名片,转身离开:“如果想好了,可以随时联系我。年轻有干劲的技术团队,我们社团最欢迎了。”
等王雷走后,三人陷入短暂的沉默。最后还是苏澈先开口:“创业……我重生前也见过不少创业案例,成功的少之又少。”
林雨菲微微点头:“不过咱们也不必急于拒绝。等再打磨下技术,再看看有没有更好的机会。”
赵大海嘿嘿一笑:“行,那就先放着。我可不想为了创业把自己逼得太紧。”
当晚,三人离开实验室时已近十点。一路上,校园路灯昏黄,微风吹拂,夏夜里有几分难得的清凉。
“Hackathon 已经过去了,可人生才刚开始。”苏澈看着星空,脑海里浮现出 2025 年前端发展的盛况:React、Angular、Vue 三足鼎立,各种生态与工具层出不穷。
“等时机成熟,我也要写一个属于我自己的框架,或者至少——让这个时代提前见识到什么叫‘响应式 + 组件化’的魅力。”
林雨菲似乎察觉到苏澈的野心,小声问道:“你接下来有什么计划?是继续完善活动平台,还是会研究更深入的东西?”
苏澈轻轻呼出一口气:“前端要学的东西太多了,ES6 只是冰山一角,还有设计模式、DOM深度操作、BOM、模块化……等我摸透这些,再想办法把我记忆里的那些前端理念落地到现实里。”
赵大海听得有点迷糊:“反正我就跟着你混了,我对这些新潮玩意儿很感兴趣。”
“别只顾跟着我,多学多看。”苏澈拍拍他的肩,“你要是自己把 ES6 学透了,以后写代码更溜,咱们团队才能更强。”
林雨菲笑道:“说得对。那我们就先各自学习,明天找时间再讨论下一步吧。”
三人分道扬镳,走向各自的宿舍。
回到宿舍,苏澈打开电脑,把今天讨论的 ES6 示例代码再次敲进笔记里,以防忘记任何关键细节。然后又翻阅一些 2014 年还不算流行的技术博客(他记得自己在 2025 年时收藏了不少精品文章),想看看有没有能提前“预见”未来风潮的蛛丝马迹。
“可惜,”苏澈苦笑,“这个时代的资料还不够多,React 才刚有风声,Angular 1.x 虽然上线,但还在摸索……Vue 更是几乎没人知道。”
他想起那位黄旭使用的 Angular + Node + Mongo 的组合,虽然有些超前,但毕竟 Angular 也不是完全不存在。而自己记忆里最熟悉的框架,却是 2025 年大火的 Vue……
“不要急。”
苏澈在心里告诫自己。他翻到笔记最后一页,写下:
目标:一年内,打造出一个基于数据驱动 + 组件化思想的前端框架雏形,并在校园内外实现落地应用。
“只有先踏实把 JavaScript 真正吃透,才能动手设计框架的核心响应式原理,不然就算写出来,也是个半吊子。”
夜已深,赵大海还在旁边呼呼大睡,宿舍灯光朦胧。苏澈放下笔,眼里闪过坚毅:
“没关系,我还有时间。既然重生到了 2014 年,命运就要由我自己来书写。”