红绿灯
问题
实现一个红绿灯控制器:红灯亮 3 秒,绿灯亮 2 秒,黄灯亮 1 秒,循环往复。
答案
这是一道经典的异步编程题,考察 Promise、async/await 和状态控制。
基础实现
使用 async/await
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function trafficLight(): Promise<void> {
while (true) {
console.log('🔴 红灯');
await sleep(3000);
console.log('🟢 绿灯');
await sleep(2000);
console.log('🟡 黄灯');
await sleep(1000);
}
}
trafficLight();
使用 Promise 链
function light(color: string, duration: number): Promise<void> {
return new Promise((resolve) => {
console.log(`${color}灯亮`);
setTimeout(resolve, duration);
});
}
function trafficLightPromise(): void {
light('红', 3000)
.then(() => light('绿', 2000))
.then(() => light('黄', 1000))
.then(trafficLightPromise); // 递归调用
}
trafficLightPromise();
使用递归
function trafficLightRecursive(index = 0): void {
const lights = [
{ color: '红', duration: 3000 },
{ color: '绿', duration: 2000 },
{ color: '黄', duration: 1000 },
];
const current = lights[index % lights.length];
console.log(`${current.color}灯亮`);
setTimeout(() => {
trafficLightRecursive(index + 1);
}, current.duration);
}
trafficLightRecursive();
带 UI 的完整实现
interface LightConfig {
color: string;
duration: number;
emoji: string;
}
class TrafficLightController {
private lights: LightConfig[] = [
{ color: 'red', duration: 3000, emoji: '🔴' },
{ color: 'green', duration: 2000, emoji: '🟢' },
{ color: 'yellow', duration: 1000, emoji: '🟡' },
];
private currentIndex = 0;
private running = false;
private timerId: ReturnType<typeof setTimeout> | null = null;
private onChangeCallback: ((light: LightConfig) => void) | null = null;
start(): void {
if (this.running) return;
this.running = true;
this.run();
}
stop(): void {
this.running = false;
if (this.timerId) {
clearTimeout(this.timerId);
this.timerId = null;
}
}
onChange(callback: (light: LightConfig) => void): void {
this.onChangeCallback = callback;
}
private run(): void {
if (!this.running) return;
const current = this.lights[this.currentIndex];
this.onChangeCallback?.(current);
this.timerId = setTimeout(() => {
this.currentIndex = (this.currentIndex + 1) % this.lights.length;
this.run();
}, current.duration);
}
getCurrentLight(): LightConfig {
return this.lights[this.currentIndex];
}
}
// 使用
const controller = new TrafficLightController();
controller.onChange((light) => {
console.log(`${light.emoji} ${light.color}灯亮,持续 ${light.duration / 1000} 秒`);
});
controller.start();
// 10 秒后停止
setTimeout(() => {
controller.stop();
console.log('红绿灯已停止');
}, 10000);
Generator 实现
function* trafficLightGenerator(): Generator<LightConfig, never, void> {
const lights: LightConfig[] = [
{ color: 'red', duration: 3000, emoji: '🔴' },
{ color: 'green', duration: 2000, emoji: '🟢' },
{ color: 'yellow', duration: 1000, emoji: '🟡' },
];
let index = 0;
while (true) {
yield lights[index % lights.length];
index++;
}
}
async function runWithGenerator(): Promise<void> {
const gen = trafficLightGenerator();
while (true) {
const { value: light } = gen.next();
console.log(`${light.emoji} ${light.color}灯`);
await sleep(light.duration);
}
}
runWithGenerator();
RxJS 实现
import { timer, concat, of, repeat } from 'rxjs';
import { mapTo, tap, delay } from 'rxjs/operators';
const red$ = of('🔴 红灯').pipe(
tap(console.log),
delay(3000)
);
const green$ = of('🟢 绿灯').pipe(
tap(console.log),
delay(2000)
);
const yellow$ = of('🟡 黄灯').pipe(
tap(console.log),
delay(1000)
);
const trafficLight$ = concat(red$, green$, yellow$).pipe(repeat());
trafficLight$.subscribe();
状态机实现
type LightState = 'red' | 'green' | 'yellow';
interface StateConfig {
duration: number;
next: LightState;
emoji: string;
}
const stateMachine: Record<LightState, StateConfig> = {
red: { duration: 3000, next: 'green', emoji: '🔴' },
green: { duration: 2000, next: 'yellow', emoji: '🟢' },
yellow: { duration: 1000, next: 'red', emoji: '🟡' },
};
async function trafficLightStateMachine(): Promise<void> {
let state: LightState = 'red';
while (true) {
const config = stateMachine[state];
console.log(`${config.emoji} ${state}灯`);
await sleep(config.duration);
state = config.next;
}
}
trafficLightStateMachine();
可配置版本
interface TrafficLightOptions {
lights: Array<{
name: string;
duration: number;
color?: string;
}>;
cycles?: number; // 循环次数,不设置则无限循环
onSwitch?: (current: string, duration: number) => void;
}
async function createTrafficLight(options: TrafficLightOptions): Promise<void> {
const { lights, cycles = Infinity, onSwitch } = options;
let cycleCount = 0;
while (cycleCount < cycles) {
for (const light of lights) {
onSwitch?.(light.name, light.duration);
await sleep(light.duration);
}
cycleCount++;
}
}
// 使用
createTrafficLight({
lights: [
{ name: '红灯', duration: 3000 },
{ name: '绿灯', duration: 2000 },
{ name: '黄灯', duration: 1000 },
],
cycles: 3, // 循环 3 次
onSwitch: (name, duration) => {
console.log(`${name}亮,持续 ${duration / 1000} 秒`);
},
});
Web Animation 实现
// 在浏览器中运行
function createTrafficLightDOM(): void {
const container = document.createElement('div');
container.style.cssText = `
display: flex;
flex-direction: column;
gap: 10px;
padding: 20px;
background: #333;
border-radius: 10px;
width: 80px;
`;
const lights = ['red', 'green', 'yellow'].map((color) => {
const light = document.createElement('div');
light.id = `light-${color}`;
light.style.cssText = `
width: 60px;
height: 60px;
border-radius: 50%;
background: #666;
transition: background 0.3s;
`;
container.appendChild(light);
return light;
});
document.body.appendChild(container);
const configs: Array<{ element: HTMLDivElement; color: string; duration: number }> = [
{ element: lights[0], color: 'red', duration: 3000 },
{ element: lights[1], color: 'green', duration: 2000 },
{ element: lights[2], color: 'yellow', duration: 1000 },
];
async function animate(): Promise<void> {
for (const config of configs) {
// 熄灭所有灯
lights.forEach((l) => (l.style.background = '#666'));
// 点亮当前灯
config.element.style.background = config.color;
await sleep(config.duration);
}
animate(); // 循环
}
animate();
}
createTrafficLightDOM();
常见面试问题
Q1: 如何取消正在运行的红绿灯?
答案:
function createCancelableTrafficLight(): {
start: () => void;
cancel: () => void;
} {
let cancelled = false;
let timerId: ReturnType<typeof setTimeout> | null = null;
const start = async (): Promise<void> => {
const lights = [
{ name: '红灯', duration: 3000 },
{ name: '绿灯', duration: 2000 },
{ name: '黄灯', duration: 1000 },
];
let index = 0;
while (!cancelled) {
const current = lights[index % lights.length];
console.log(current.name);
await new Promise<void>((resolve) => {
timerId = setTimeout(resolve, current.duration);
});
if (cancelled) break;
index++;
}
};
const cancel = (): void => {
cancelled = true;
if (timerId) {
clearTimeout(timerId);
}
};
return { start, cancel };
}
const { start, cancel } = createCancelableTrafficLight();
start();
setTimeout(cancel, 5000); // 5 秒后取消
Q2: 如何实现暂停和恢复?
答案:
class PausableTrafficLight {
private paused = false;
private resolveResume: (() => void) | null = null;
async start(): Promise<void> {
const lights = ['🔴', '🟢', '🟡'];
const durations = [3000, 2000, 1000];
let index = 0;
while (true) {
// 检查暂停
if (this.paused) {
await new Promise<void>((resolve) => {
this.resolveResume = resolve;
});
}
console.log(lights[index]);
await sleep(durations[index]);
index = (index + 1) % lights.length;
}
}
pause(): void {
this.paused = true;
}
resume(): void {
this.paused = false;
this.resolveResume?.();
}
}
Q3: 为什么不用 setInterval?
答案:
| 方案 | 问题 |
|---|---|
setInterval | 间隔固定,不适合不同时长 |
setTimeout 递归 | 灵活控制每次时长 |
async/await | 代码简洁,易于控制 |
// ❌ setInterval 难以实现不同时长
setInterval(() => {
// 无法方便地切换 3s、2s、1s
}, 1000);
// ✅ setTimeout 或 async/await
async function run() {
await sleep(3000); // 红灯
await sleep(2000); // 绿灯
await sleep(1000); // 黄灯
}