JavaScript非同步流程控制的前世今生
使用過js的開發者都知道JavaScript是單執行緒的,所謂的單執行緒是一次只能完成一種任務,若還有其他任務,需等待前一個任務執行完成後再來執行該任務。那麼問題來了,假設我需要同時執行多個任務呢,單執行緒又如何能滿足這種需求呢?
js引入了同步和非同步,所謂同步就是上述所說的,另一個任務需要等待當前任務完成之後再執行,而非同步呢指的是當前任務有一個或多個回撥函式,當該任務執行完會觸發回撥函式的執行,而後一個任務也不必等待前一個任務執行完再執行,它的執行與前一個任務是否執行完無關。
我們常常遇到的就是瀏覽器的ajax請求,它是一種耗時操作,在該瀏覽器向伺服器傳送請求資料時,不必等待伺服器返回資料,也可以做一些其它的操作或者任務,等伺服器返回資料,會觸發一個回撥函式通知資料已經被請求回來了。
接下來我們通過一個例子一起來看下關於js非同步流程的前世今生:
假設有3個小球A,B,C,A球在4ms內向右移動100px,接著是B球以同樣時間,移動同樣的距離,接著是C。
回撥函式
以下給出部分實現的關鍵程式碼,這是一種很常規的使用回撥函式來解決非同步流程的問題,先執行完小球A,然後執行小球A成功的回撥函式,一層一層依次巢狀,直至小球C。
doSport() { // 1.回撥 this.move(this.ref1, 100, () => { this.move(this.ref2, 100, () => { this.move(this.ref3, 100, () => { console.log('stop sport'); }) }) }) } move(ele, target, callback) { let that = this; return new Promise(function(resolve, reject){ let count = 0; let timer = setInterval(() => { if (count < target) { ele.current.style.transform = `translateX(${++count}px)`; } else { clearInterval(timer); resolve(); callback && callback(); } }, 4); }) }
可以看出上述程式碼存在一些問題,如果有10個還好,20個呢甚至100個小球呢?那麼是不是會造成程式碼地獄呢,層級巢狀太深,雖然可讀性好,但是非常難維護,審美也很差。
Promise
於是有了Promise的實現方式,通過then方法執行上述回撥函式。
doSport() { // 2.promise this.move(this.ref1, 100).then(() => { this.move(this.ref2, 100).then(() => { this.move(this.ref3, 100).then(() => { console.log('stop sport'); }) }) }); } move(ele, target, callback) { let that = this; return new Promise(function(resolve, reject){ let count = 0; let timer = setInterval(() => { if (count < target) { ele.current.style.transform = `translateX(${++count}px)`; } else { clearInterval(timer); resolve(); callback && callback(); } }, 4); }) }
move方法返回一個promise,通過then方法執行它的下一步操作。promise是CommonJS工作提出的一種規範,目的是為非同步程式設計提供統一介面,它的思想是,每一個非同步任務返回一個promise,then方法處理下一步的操作,成功態執行resolve函式,失敗態就執行reject函式。
Generator
接著提出了Generator函式,在函式名前加上*,內部函式結合yield,當然最好的是搭配上co函式,該函式是可以幫你自動執行迭代器,從下面的程式碼我們可以看到通過用yield的非同步方式實現同步操作,結合co返回一個promise,最後通過then方法執行任務成功的函式。
// 3.generator *doSport() { yield this.move(this.ref1, 100); yield this.move(this.ref2, 100); yield this.move(this.ref3, 100); } // co:自動執行完迭代器 co(it) { return new Promise(function(resolve, reject) { function next(d) { let { value, done } = it.next(d); if (!done) { value.then(function(data) { next(data) }, reject); } else { resolve(value) } } next() }) } co(doSport()).then(function() { alert('ok'); }) move(ele, target, callback) { let that = this; return new Promise(function(resolve, reject){ let count = 0; let timer = setInterval(() => { if (count < target) { ele.current.style.transform = `translateX(${++count}px)`; } else { clearInterval(timer); resolve(); callback && callback(); } }, 4); }) }
由於generator函式需要搭配co函式,要手動的執行內部的next方法,而且不夠直觀,沒有語義化。
Async/await
es7推出了Async/await的新語法,而且babel也支援,用async修飾函式,內部結合使用await,這種方式我個人覺得很優雅,很容易使用,其實它的內部實現也是gennerator
// 4.async/await async doSport() { await this.move(this.ref1, 100); await this.move(this.ref2, 100); await this.move(this.ref3, 100); } move(ele, target, callback) { let that = this; return new Promise(function(resolve, reject){ let count = 0; let timer = setInterval(() => { if (count < target) { ele.current.style.transform = `translateX(${++count}px)`; } else { clearInterval(timer); resolve(); callback && callback(); } }, 4); }) }
async函式可以看做多個非同步操作,包裝成一個promise物件,而await就是內部then方法的語法糖,相比較於gennerator函式來說,async/await函式更加直觀,語義化,而且更加好用,不需要co函式。
總結
總的來說,不同的非同步方案都是解決非同步流程的方式,但是非同步程式設計的本質並沒有變,只是對於開發者來說更加的友好,易於維護。時代在發展,技術也在日益更新,我們更重要的是要掌握背後的程式設計思想原理,這樣才能更上一層樓。