异步编程与流程控制的终极考验,这样回答直接拿到Offer!
引子:一场突如其来的面试题
"假设我们要实现一个红绿灯功能,红灯亮1秒,黄灯亮3秒,绿灯亮2秒,循环往复。你会怎么实现?"
面试官推了推眼镜,嘴角带着一丝不易察觉的微笑。这看似简单的前端面试题,却暗藏着对异步编程、流程控制乃至内存管理的全面考察。
我深吸一口气,开始了我的回答...
基础版:从setTimeout到Promise
最初的直觉:回调地狱
"最直观的做法可能是使用setTimeout嵌套:"
function trafficLight() {
console.log('红灯');
setTimeout(() => {
console.log('黄灯');
setTimeout(() => {
console.log('绿灯');
setTimeout(() => {
trafficLight(); // 循环
}, 2000);
}, 3000);
}, 1000);
}
"但这种做法会陷入可怕的'回调地狱',代码难以维护且容易出错。"
Promise化:异步编程的救星
"更优雅的方式是使用Promise:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function trafficLight() {
while(true) {
console.log('红灯');
await sleep(1000);
console.log('黄灯');
await sleep(3000);
console.log('绿灯');
await sleep(2000);
}
}
"这里我们创建了一个sleep函数,它返回一个在指定时间后resolve的Promise,然后使用async/await以同步的方式编写异步代码。"
进阶版:可配置与可扩展
面试官点点头,但显然期待更多:"如果红绿灯的时间需要动态配置呢?"
配置化设计
"我们可以将配置抽离出来,使代码更加灵活:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function trafficLight(config) {
const { red = 1000, yellow = 3000, green = 2000 } = config || {};
while(true) {
console.log('红灯');
await sleep(red);
console.log('黄灯');
await sleep(yellow);
console.log('绿灯');
await sleep(green);
}
}
函数式链式调用
"或者采用更函数式的写法:
function light(color, ms) {
console.log(color);
return new Promise(resolve => setTimeout(resolve, ms));
}
function runTrafficLight() {
light('红灯', 1000)
.then(() => light('黄灯', 3000))
.then(() => light('绿灯', 2000))
.then(runTrafficLight); // 递归调用实现循环
}
"这种方法利用了Promise的链式调用特性,代码更加清晰。"
高级版:可控性与内存安全
面试官眼中闪过一丝赞许,但抛出了更棘手的问题:"如果用户突然离开页面,如何避免内存泄漏?如何中止红绿灯?"
AbortController:异步操作的中断机制
"这是一个非常重要的问题!在React等前端框架中,组件卸载后如果异步操作仍在继续,确实会导致内存泄漏。现代浏览器提供了AbortController来解决这个问题:
const sleep = (ms, signal) => new Promise((resolve, reject) => {
const timer = setTimeout(resolve, ms);
// 监听中止信号
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new DOMException('Aborted', 'AbortError'));
}, { once: true });
});
async function trafficLight(signal) {
const seq = [
{ color: 'red', ms: 1000 },
{ color: 'yellow', ms: 3000 },
{ color: 'green', ms: 2000 }
];
while (!signal.aborted) {
for (const { color, ms } of seq) {
if (signal.aborted) return; // 检查是否已中止
console.log(color);
try {
await sleep(ms, signal);
} catch (e) {
if (e.name === 'AbortError') {
console.log('红绿灯已停止');
return;
}
throw e;
}
}
}
}
// 使用示例
let controller = new AbortController();
trafficLight(controller.signal);
// 需要停止时
controller.abort();
总结与反思
面试官满意地点点头,我知道这道题我答得不错。
红绿灯问题考察的核心知识点
- 异步编程能力:对Promise、async/await的掌握程度
- 代码设计能力:如何组织代码使其可维护、可扩展
- 内存安全意识:对内存泄漏的认识和防范措施
- API熟悉度:对现代浏览器API(如AbortController)的了解
- 系统设计思维:如何将需求转化为合理的代码结构
实战建议
- 从简单开始:先实现基础功能,再逐步添加高级特性
- 考虑边界情况:中止、错误处理等都是展示你全面思维的机会
- 紧跟现代API:AbortController等新API能极大提升代码质量
- 理解问题本质:红绿灯问题本质是状态管理和异步流程控制
结语
红绿灯问题看似简单,实则内涵丰富。它不仅能考察候选人的编码能力,还能反映其代码设计思维和对现代WebAPI的掌握程度。下一次遇到这个问题,希望你能像交通指挥员一样,从容不迫地指挥异步流程的"车辆"有序通行!