大涛子客栈

题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯两秒亮一次,不断交替循环

先定义下红绿灯:

1
2
3
4
5
6
7
8
9
function red() {
console.log("red");
}
function green() {
console.log("green");
}
function yellow() {
console.log("yellow");
}

异步编程的语法目标,就是怎样让它更像同步编程,有以下几种:

  • 回调函数实现
  • 事件监听 event
  • 发布订阅 Publish/Subscribe
  • Promise 和 Generator
  • Async/await

一、回调函数

这是最常见的一种方式,把函数作为参数送入,然后回调。

第一版:简单明了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function step() {
console.log("wait for about 3 seconds...");
setTimeout(() => {
red();
setTimeout(() => {
green();
setTimeout(() => {
yellow();
step();
}, 2000);
}, 1000);
}, 3000);
}

step();

第二版:封装定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var light = (timmer, cb) => {
setTimeout(() => {
cb();
}, timmer);
};

function step(cb) {
light(3000, () => {
red();
light(1000, () => {
green();
light(2000, () => {
yellow();
step();
});
});
});
typeof cb === "function" && cb();
}

step(() => console.log("wait for about 3 seconds..."));

二、事件监听

采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

第一版:监听一个事件,然后触发这个事件,并且执行事件里的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 引入 Node events 模块
const events = require("events");
const emitter = new events.EventEmitter();

// 监听
emitter.on("lightEvent", str => console.log(str));

// 触发
emitter.emit("lightEvent", "red");
emitter.emit("lightEvent", "green");
emitter.emit("lightEvent", "yellow");

// 输出
// red
// green
// yellow

第二版:加个顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 引入 Node events 模块
const events = require("events");
const emitter = new events.EventEmitter();

const lightHandler = (timmer, cb) => {
setTimeout(() => {
cb();
}, timmer);
};

// 监听
emitter.on("lightEvent", str => console.log(str));

// 触发
function step() {
lightHandler(3000, () => {
emitter.emit("lightEvent", "red");
lightHandler(1000, () => {
emitter.emit("lightEvent", "green");
lightHandler(2000, () => {
emitter.emit("lightEvent", "yellow");
step();
});
});
});
}

step();

依旧是回调执行,我们继续远征吧。

三、发布/订阅

“事件”,完全可以理解成”信号”。

我们假定,存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”(publish)一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称”观察者模式”(observer pattern)。 - 阮一峰

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

第一版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const publisher = {
// 缓存列表
lists: {},
// 订阅
subscribe: function(event, handler) {
(this.lists[event] || (this.lists[event] = [])).push(handler);
},
// 发布
publish: function() {
const event = [].shift.call(arguments);
const events = this.lists[event];

if (!events || events.length === 0) {
return false;
}

events.forEach(item => {
item.apply(this, arguments);
});
}
};

// 订阅
publisher.subscribe("lightEvent", red);
publisher.subscribe("lightEvent", green);
publisher.subscribe("lightEvent", yellow);

// 发布
publisher.publish("lightEvent");

第二版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
const publisher = {
// 缓存列表
lists: {},
// 订阅
subscribe: function(event, handler) {
(this.lists[event] || (this.lists[event] = [])).push(handler);
},
// 取消订阅
unsubscribe: function(event, handler) {
const events = this.lists[event];
if (!events) {
return false;
}
if (!handler) {
events && (events.length = 0);
} else {
events.forEach((item, i) => {
if (item === handler) {
events.splice(i, 1);
}
});
}
},
// 发布
publish: function() {
const event = [].shift.call(arguments);
const events = this.lists[event];

if (!events || events.length === 0) {
return false;
}

events.forEach(item => {
item.apply(this, arguments);
});
}
};

const lightHandler = (timmer, cb) => {
setTimeout(() => {
cb();
}, timmer);
};

const colorHandler = color => console.log(color);

// 订阅
publisher.subscribe("redEvent", colorHandler);
publisher.subscribe("greenEvent", colorHandler);
publisher.subscribe("yellowEvent", colorHandler);

function step() {
lightHandler(3000, () => {
publisher.publish("redEvent", "red");
lightHandler(1000, () => {
publisher.publish("greenEvent", "green");
lightHandler(2000, () => {
publisher.publish("yellowEvent", "yellow");
step();
});
});
});
}

step();

三、Promise

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var light = (timmer, cb) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
cb();
resolve();
}, timmer);
});
};

var step = () => {
Promise.resolve()
.then(() => {
return light(3000, red);
})
.then(() => {
return light(1000, green);
})
.then(() => {
return light(2000, yellow);
})
.then(() => {
step();
})
.catch(err => console.log(err));
};

step();

四、Generator

Promise 的写法减少了好多回调,但是仍有回调的存在,这次尝试使用 Generator,看是否能够避免回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const light = (timmer, cb) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
cb();
resolve();
}, timmer);
});
};

function* gen() {
yield light(3000, red);
yield light(1000, green);
yield light(3000, yellow);
}

const iterator = gen();

const step = (gen, iterator) => {
const s = iterator.next();
// 返回 { value: Promise { <pending> }, done: false }
if (s.done) {
step(gen, gen());
} else {
// value 返回 Promise 对象
s.value.then(() => {
step(gen, iterator);
});
}
};

step(gen, iterator);

五、Async/await

有了 Generator 做铺垫,async/await 就比较容易理解了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const light = (timmer, cb) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
cb();
resolve();
}, timmer);
});
};

async function step() {
await light(3000, red);
await light(1000, green);
await light(2000, yellow);
step();
}

step();

同步写法,容易理解,和我们的线性思考方式一致,async/awaitES2017 的方案。

学习资料

 评论