1. 程式人生 > 前端設計 >手摸手,帶你探究Javascript非同步程式設計

手摸手,帶你探究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的冗餘,非同步程式碼以同步的方式優雅的呈現了出來。
如果大家發現本文的程式碼有錯誤或疏漏之處,歡迎大家指正。