瞭解javascript的同窗想必對同步和異步的概念應該都很熟悉了,若是還有不熟悉的同窗,我這裏舉個形象的例子,好比咱們早上起牀後要幹三件事:燒水、洗臉、吃早飯,同步至關於咱們先燒水,水燒開了再洗臉,洗完臉再吃早飯,三件事順序執行,一件幹完了再幹下一件;而異步至關於咱們在燒水的同時吃早飯(不洗臉就吃早飯不太衛生),吃完早飯再洗臉。顯然異步比同步更加高效,省去了不少等待的時間,同步過程的執行時間取決於全部行爲的總和,而異步過程的執行時間只取決於最長的那個行爲,以下圖所示:javascript
因爲Javascript是單線程的,同時只能處理一件事,在上面的例子中這個單線程就是「我」,好比我不能同時洗臉和吃早飯同樣。因此爲了讓執行效率提升,咱們要儘可能讓這個線程一直處於忙碌狀態而不是閒置狀態,就像咱們不用幹等燒水,能夠同時去作其餘事情,而燒水由系統的其餘線程去處理(該線程不屬於Javascript)。在計算機的世界中,不少I/O密集型的操做是須要等待的,好比網絡請求、文件讀寫等,因此異步方法在處理這些操做會更加駕輕就熟。java
瞭解異步的意義以後,咱們來對比目前主流幾種異步過程控制方法,探討一下異步編程的最佳實踐。git
首先callback和異步沒有必然聯繫,callback本質就是類型爲function的函數參數,對於該callback是同步仍是異步執行則取決於函數自己。雖然callback經常使用於異步方法的回調,但其實有很多同步方法也能夠傳入callback,好比最多見的數組的forEach
方法:es6
var arr = [1, 2, 3]; arr.forEach(function (val) { console.log(val); }); console.log('finish'); // 打印結果:1,2,3,finish
相似的還有數組的map
, filter
, reduce
等不少方法。github
常見的異步callback如setTimeout
中的回調:編程
setTimeout(function () { console.log("time's up"); }, 1000); console.log('finish'); // 打印結果:finish, time's up
若是咱們將延遲時間改成0
,打印結果仍將是finish, time's up
,由於異步callback會等函數中的同步方法都執行完成後再執行。數組
在實際項目中咱們常常會遇到這樣的問題:下一步操做依賴於上一步操做的結果,上一步操做又依賴於上上步操做,而每一步操做都是異步的。。這樣遞進的層級多了會造成不少層callback嵌套,致使代碼可讀性和可維護性變的不好,造成所謂的Callback Hell,相似這樣:promise
step1(param, function (result1) { step2(result1, function (result2) { step3(result2, function (result3) { step4(result3, function (result4) { done(result4); }) }) }) })
固然在不放棄使用callback的前提下,上面的代碼仍是有優化空間的,咱們能夠將它從新組織一下:瀏覽器
step1(param, callbac1); function callback1(result1){ step2(result1, callback2); } function callback2(result2){ step3(result2, callback3); } function callback3(result3){ step4(result3, callback4); } function callback4(result4){ done(result4); }
至關於將Callback Hell的橫向深度轉化爲代碼的縱向高度,變得更接近於咱們習慣的由上到下的同步調用, 複雜度沒有變,只是看起來更清晰了,缺點就是要定義額外的函數、變量。將這一思想進一步延伸就有了下面的Promise。babel
Promise中文譯爲「承諾」,在Javascript中是一個抽象的概念,表明當前沒有實現,但將來的某個時間點會(也可能不會)實現的一件事。舉個實例化的例子:早上燒水,我給你一個承諾(Promise),十分鐘後水能燒開,若是一切正常,10分鐘以後水確實能燒開,表明這個promise兌現了(fullfilled),可是若是中途停電了,10分鐘水沒燒開,那這個promise兌現失敗(rejected)。用代碼能夠表示爲:
const boilWaterInTenMins = new Promise(function (resolve, reject) { boiler.work(function (timeSpent) { if (timeSpent <= 10) { resolve(); } else { reject(); } }); });
若是想提升瀏覽器對Promise的兼容性可使用babel或者第三方的實現(參考 github awesome promise)
咱們再來看Promise對於異步過程控制有怎樣的提高,還基於上面Callback Hell的例子,若是用Promise實現會如何呢?
首先咱們須要將step1
~ done
的函數用Promise實現(即返回一個Promise),而後進行一連串的鏈式調用就能夠了:
stepOne(param) .then((result1) => { return step2(result1) }) .then((result2) => { return step3(result2) }) .then((result3) => { return step4(result3) }) .then((result4) => { return done(result4) }) .catch(err => handleError(err));
是否是簡單不少!
若是你不太習慣Promise的調用方式,那咱們能夠用async/await將其轉化成更接近同步調用的方式:
async function main() { try { var result1 = await step1(param); var result2 = await step2(result1); var result3 = await step3(result2); var result4 = await step4(result3); done(result4); } catch (err) { handleError(err); } } main();
Generator是一個更加抽象的概念,要弄懂什麼是Generator首先要理解另外幾個概念Iterable Protocol(可迭代協議),Iterator Protocol(迭代器協議)和 Iterator(迭代器)。
Iterable Protocol 的特色能夠歸納爲:
Symbol.iterator
的方法for...of
遍歷Javascript Array就實現了Iterable Protocol,除了常規的取值方式,咱們也能夠利用array的Symbol.iterator
:
var arr = [1, 2, 3]; var iterator = arr[Symbol.iterator](); iterator.next(); // {value: 1, done: false}
咱們也能夠修改Array默認的迭代方式,好比返回兩倍的值:
Array.prototype[Symbol.iterator] = function () { var nextIndex = 0; var self = this; return { next: function () { return nextIndex < self.length ? { value: self[nextIndex++] * 2, done: false } : { done: true } } }; } for(let el of [1, 2, 3]){ console.log(el); } // 輸出:2,4,6
Iterator Protocol 的特色能夠歸納爲:
{value: any, done: boolean}
value
爲返回值,done
爲true
時value
能夠省略done
爲true
表示迭代結束,此時value
表示最終返回值done
爲false
,則能夠繼續迭代,產生下一個值顯然Iterator就是實現了Iterator Protocol的對象。
理解上面幾個概念後,理解Generator就簡單多了,generator的特色可歸納爲:
最簡單的generator function好比:
function* gen() { var x = yield 5 + 6; } var myGen = gen(); // myGen 就是一個generator
咱們能夠調用next方法來得到yield
表達式的值:
myGen.next(); // { value: 11, done: false }
但此時x
並無被賦值,能夠想象成javascript執行完 yield 5 + 6
就停住了,爲了繼續執行賦值操做咱們須要再次調用next
,並將獲得的值回傳:
function* gen() { var x = yield 5 + 6; console.log(x); // 11 } var myGen = gen(); console.log(myGen.next()); // { value: 11, done: false } console.log(myGen.next(11)); // { value: undefined, done: true }
說了這麼多,generator和異步到底有什麼關係呢?咱們來看Promise + Generator 實現的異步控制(step1 ~ done 返回Promise):
genWrap(function* () { var result1 = yield step1(param); var result2 = yield step2(result1); var result3 = yield step3(result2); var result4 = yield step4(result3); var result5 = yield done(result4); }); function genWrap(genFunc) { var generator = genFunc(); function handle(yielded) { if (!yielded.done) { yielded.value.then(function (result) { return handle(generator.next(result)); }); } } return handle(generator.next()); }
和async/await相似,這種實現也將異步方法轉化成了同步的寫法,實際上這就是 ES7中async/await的實現原理(將genWrap替換爲async,將yield替換成await)。
但願本文對你們有點幫助,能更深入的理解javascript異步編程,能寫出更優雅更高效的代碼。有錯誤歡迎指正。新年快樂!