手摸手,帶你探究Javascript非同步程式設計
從一個紅綠燈問題來學習非同步程式設計
問題描述:一個路口的紅綠燈,會按照你綠燈亮10秒,黃燈亮2秒,紅燈亮5秒的順序無限迴圈,請編寫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()去實現迴圈的,但是它有一個最大的弊端就是需要寫間隔的總時間,相對而言,並沒有遞迴來得簡潔優雅。
這裡我們也可以看到,用setTimeout去實現的話就是無腦巢狀,但是需要迴圈的元素很多的話,就會陷入“回撥地獄”,“地獄模式啊,筒子們!”
為了幫助大家擺脫“地獄”,回到“人間”,ES6將promise寫入了規範,promise最大的優勢就是採用鏈式呼叫,解決了回撥地域問題。
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返回的執行結果(resolve,或者reject),來決定繼續執行then裡面的程式碼還是執行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,t);
})
}
async function go() {
while(true) {
green();
await sleep(10000);
yellow();
await sleep(2000);
red();
await sleep(5000);
}
}
go();
複製程式碼
看到這裡,你是不是有一種“刪繁就簡”爽快感。沒有了被“回撥地獄”支配的恐懼,也沒有了then的冗餘,非同步程式碼以同步的方式優雅的呈現了出來。
如果大家發現本文的程式碼有錯誤或疏漏之處,歡迎大家指正。