從一個紅綠燈問題來學習異步編程
問題描述:一個路口的紅綠燈稽亏,會按照你綠燈亮10秒壶冒,黃燈亮2秒,紅燈亮5秒的順序無限循環(huán)截歉,請編寫JS代碼來控制這個紅綠燈
話不多說胖腾,首先我們肯定要實現紅綠燈的展示,這部分比較基礎瘪松,直接上代碼
// CSS部分
div {
background-color: gray;
display: inline-block;
margin: 30px;
height: 100px;
width: 100px;
border-radius: 50%;
}
.green.light {
background-color: green;
}
.yellow.light {
background-color: yellow;
}
.red.light {
background-color: red;
}
// HTML部分
<div class="green"></div>
<div class="yellow"></div>
<div class="red"></div>
// JS部分
function green() {
let lights = document.getElementsByTagName("div");
for (let i = 0; i < 3; i++) {
lights[i].classList.remove("light");
document.getElementsByClassName("green")[0].classList.add("light");
}
}
function yellow() {
let lights = document.getElementsByTagName("div");
for (let i = 0; i < 3; i++) {
lights[i].classList.remove("light");
document.getElementsByClassName("yellow")[0].classList.add("light");
}
}
function red() {
let lights = document.getElementsByTagName("div");
for (let i = 0; i < 3; i++) {
lights[i].classList.remove("light");
document.getElementsByClassName("red")[0].classList.add("light");
}
}
接下來咸作,我們先思考一下如何每隔一段時間去點亮一個燈,并且讓其他燈變灰宵睦。最簡單的辦法就是用setTimeout()去實現
function go() {
green();
setTimeout(() => {
yellow();
setTimeout(() => {
red();
setTimeout(() => {
go()
}, 5000)
}, 2000);
}, 10000);
}
go();
剛開始我用的是setInterval()去實現循環(huán)的记罚,但是它有一個最大的弊端就是需要寫間隔的總時間,相對而言壳嚎,并沒有遞歸來得簡潔優(yōu)雅桐智。
這里我們也可以看到末早,用setTimeout去實現的話就是無腦嵌套,但是需要循環(huán)的元素很多的話说庭,就會陷入“回調地獄”然磷,“地獄模式啊,筒子們刊驴!”
為了幫助大家擺脫“地獄”姿搜,回到“人間”,ES6將promise寫入了規(guī)范缺脉,promise最大的優(yōu)勢就是采用鏈式調用痪欲,解決了回調地域問題。
function sleep(t) {
return new Promise((resolve, reject) => {
setTimeout(resolve, t);
})
}
function go() {
green();
sleep(10000).then(() => {
yellow();
return sleep(2000);
}).then(() => {
red();
return sleep(5000);
}).then(go).catch(err => {
console.log('出錯啦攻礼!')
});
}
go();
函數會根據上一個promise返回的執(zhí)行結果(resolve,或者reject),來決定繼續(xù)執(zhí)行then里面的代碼還是執(zhí)行catch里面的代碼业踢。
promise相對于setTimeout來說,明顯的避免了“回調地獄”問題礁扮,但是
也有弊端知举,最直白的就是有很多then,使代碼非常冗余太伊,不夠簡潔和語義化雇锡。
那么怎么干掉then,將異步代碼偽裝的像同步代碼呢?前輩們采用generator函數去解決這個問題僚焦。
function sleep(t) {
return new Promise((resolve, reject) => {
setTimeout(resolve, t);
});
}
function* go() {
while(true) {
green();
yield sleep(10000);
yellow();
yield sleep(2000);
red();
yield sleep(5000);
}
}
但是generator的調用就需要借助co框架去實現了锰提,下面是co框架的實現思路
function run(iterator) {
let {value, done} = iterator.next();
if (done) {
return;
}
if (value instanceof Promise) {
value.then(() => {
run(iterator);
})
}
}
function co(generator) {
return function() {
return run(generator());
}
}
go = co(go);
go();
我們可以看到使用generator函數確實更加語義化了,但是需要引進co框架芳悲,你可能會想:“就一個紅綠燈問題我還得引進一個co框架立肘,內啥,我40米的大刀呢名扛?”
ES2017 標準引入了 async 函數谅年,使得異步操作變得更加方便。async 函數是什么肮韧?一句話融蹂,它就是 Generator 函數的語法糖。
接下來我們用async函數來實現紅綠燈問題
function sleep(t) {
return new Promise((resolve, reject) => {
setTimeout(resolve, t);
})
}
async function go() {
while(true) {
green();
await sleep(10000);
yellow();
await sleep(2000);
red();
await sleep(5000);
}
}
go();
看到這里弄企,你是不是有一種“刪繁就簡”爽快感超燃。沒有了被“回調地獄”支配的恐懼,也沒有了then的冗余桩蓉,異步代碼以同步的方式優(yōu)雅的呈現了出來淋纲。
如果大家發(fā)現本文的代碼有錯誤或疏漏之處,歡迎大家指正院究。