跳到主要内容

红绿灯

问题

实现一个红绿灯控制器:红灯亮 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); // 黄灯
}

相关链接