手摸手,帶你探究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()去實現promise

function go() {
	green();
	setTimeout(() => {
		yellow();
		setTimeout(() => {
			red();
			setTimeout(() => {
				go()
			}, 5000)
		}, 2000);
	}, 10000);
}

go();
複製代碼

剛開始我用的是setInterval()去實現循環的,可是它有一個最大的弊端就是須要寫間隔的總時間,相對而言,並無遞歸來得簡潔優雅。
這裏咱們也能夠看到,用setTimeout去實現的話就是無腦嵌套,可是須要循環的元素不少的話,就會陷入「回調地獄」,「地獄模式啊,筒子們!」
爲了幫助你們擺脫「地獄」,回到「人間」,ES6將promise寫入了規範,promise最大的優點就是採用鏈式調用,解決了回調地域問題。bash

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函數來實現紅綠燈問題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的冗餘,異步代碼以同步的方式優雅的呈現了出來。
若是你們發現本文的代碼有錯誤或疏漏之處,歡迎你們指正。異步編程

相關文章
相關標籤/搜索