知其然知其因此然,首先了解三個概念:javascript
1.什麼是同步?java
所謂同步,就是在發出一個"調用"時,在沒有獲得結果以前,該「調用」就不返回。可是一旦調用返回,就獲得返回值了。換句話說,就是由「調用者」主動等待這個「調用」的結果。此調用執行完以前,阻塞以後的代碼執行。node
2.什麼是異步?git
"調用"在發出以後,這個調用就直接返回了,因此沒有返回結果。換句話說,當一個異步過程調用發出後,調用者不會馬上獲得結果。而是在"調用"發出後,"被調用者"經過狀態、通知來通知調用者,或經過回調函數處理這個調用。異步調用發出後,不影響後面代碼的執行。es6
3.JavaScript 中爲何須要異步?github
首先咱們知道JavaScript是單線程的(即便新增了webworker,可是本質上JS仍是單線程)。同步代碼意味着什麼呢?意味着有可能會阻塞,當咱們有一個任務須要時間較長時,若是使用同步方式,那麼就會阻塞以後的代碼執行。而異步則不會,咱們不會等待異步代碼的以後,繼續執行異步任務以後的代碼。web
更多優質文章可戳: https://github.com/YvetteLau/...ajax
概念瞭解完了,咱們就要進入今天的正題了。首先你們思考一下:平時在工做中,主要使用了哪些異步解決方案,這些異步方案有什麼優缺點?編程
異步最先的解決方案是回調函數,如事件的回調,setInterval/setTimeout中的回調。可是回調函數有一個很常見的問題,就是回調地獄的問題(稍後會舉例說明);segmentfault
爲了解決回調地獄的問題,社區提出了Promise解決方案,ES6將其寫進了語言標準。Promise必定程度上解決了回調地獄的問題,可是Promise也存在一些問題,如錯誤不能被try catch,並且使用Promise的鏈式調用,其實並無從根本上解決回調地獄的問題,只是換了一種寫法。
ES6中引入 Generator 函數,Generator是一種異步編程解決方案,Generator 函數是協程在 ES6 的實現,最大特色就是能夠交出函數的執行權,Generator 函數能夠看出是異步任務的容器,須要暫停的地方,都用yield語句註明。可是 Generator 使用起來較爲複雜。
ES7又提出了新的異步解決方案:async/await,async是 Generator 函數的語法糖,async/await 使得異步代碼看起來像同步代碼,異步編程發展的目標就是讓異步邏輯的代碼看起來像同步同樣。
回調函數 ---> Promise ---> Generator ---> async/await.
//node讀取文件 fs.readFile(xxx, 'utf-8', function(err, data) { //code });
回調函數的使用場景(包括但不限於):
回調函數的優勢: 簡單。回調函數的缺點:
異步回調嵌套會致使代碼難以維護,而且不方便統一處理錯誤,不能 try catch
和 回調地獄(如先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C...)。
fs.readFile(A, 'utf-8', function(err, data) { fs.readFile(B, 'utf-8', function(err, data) { fs.readFile(C, 'utf-8', function(err, data) { fs.readFile(D, 'utf-8', function(err, data) { //.... }); }); }); });
Promise 必定程度上解決了回調地獄的問題,Promise 最先由社區提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
那麼咱們看看Promise是如何解決回調地獄問題的,仍然以上文的readFile 爲例(先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C)。
function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, 'utf8', (err, data) => { if(err) reject(err); resolve(data); }); }); } read(A).then(data => { return read(B); }).then(data => { return read(C); }).then(data => { return read(D); }).catch(reason => { console.log(reason); });
Promise 的優勢:
缺點:
try catch
假設有這樣一個需求:讀取A,B,C三個文件內容,都讀取成功後,再輸出最終的結果。在Promise以前,咱們通常能夠藉助發佈訂閱模式去實現:
let pubsub = { arry: [], emit() { this.arry.forEach(fn => fn()); }, on(fn) { this.arry.push(fn); } } let data = []; pubsub.on(() => { if(data.length === 3) { console.log(data); } }); fs.readFile(A, 'utf-8', (err, value) => { data.push(value); pubsub.emit(); }); fs.readFile(B, 'utf-8', (err, value) => { data.push(value); pubsub.emit(); }); fs.readFile(C, 'utf-8', (err, value) => { data.push(value); pubsub.emit(); });
Promise給咱們提供了 Promise.all
的方法,對於這個需求,咱們可使用 Promise.all
來實現。
/** * 將 fs.readFile 包裝成promise接口 */ function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, 'utf8', (err, data) => { if(err) reject(err); resolve(data); }); }); } /** * 使用 Promise * * 經過 Promise.all 能夠實現多個異步並行執行,同一時刻獲取最終結果的問題 */ Promise.all([ read(A), read(B), read(C) ]).then(data => { console.log(data); }).catch(err => console.log(err));
可執行代碼可戳: https://github.com/YvetteLau/...
Generator 函數是 ES6 提供的一種異步編程解決方案,整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操做須要暫停的地方,都用 yield 語句註明。
Generator 函數通常配合 yield 或 Promise 使用。Generator函數返回的是迭代器。對生成器和迭代器不瞭解的同窗,請自行補習下基礎。下面咱們看一下 Generator 的簡單使用:
function* gen() { let a = yield 111; console.log(a); let b = yield 222; console.log(b); let c = yield 333; console.log(c); let d = yield 444; console.log(d); } let t = gen(); //next方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值 t.next(1); //第一次調用next函數時,傳遞的參數無效 t.next(2); //a輸出2; t.next(3); //b輸出3; t.next(4); //c輸出4; t.next(5); //d輸出5;
爲了讓你們更好的理解上面代碼是如何執行的,我畫了一張圖,分別對應每一次的next方法調用:
仍然以上文的 readFile (先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C)爲例,使用 Generator + co庫來實現:
const fs = require('fs'); const co = require('co'); const bluebird = require('bluebird'); const readFile = bluebird.promisify(fs.readFile); function* read() { yield readFile(A, 'utf-8'); yield readFile(B, 'utf-8'); yield readFile(C, 'utf-8'); //.... } co(read()).then(data => { //code }).catch(err => { //code });
Generator的缺點大約不用我說了,除非是找虐,否則通常不會直接使用 Generator 來解決異步的(固然也不排除是由於我不熟練)~~~
不使用co庫,如何實現?可否本身寫一個最簡的 my_co,有助於理解 async/await 的實現原理 ?請戳: https://github.com/YvetteLau/...
PS: 若是你還不太瞭解 Generator/yield,建議閱讀ES6相關文檔。
ES7中引入了 async/await 概念。async 實際上是一個語法糖,它的實現就是將 Generator函數和自動執行器(co),包裝在一個函數中。
async/await 的優勢是代碼清晰,不用像 Promise 寫不少 then 鏈,就能夠處理回調地獄的問題。而且錯誤能夠被try catch。
仍然以上文的readFile (先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C) 爲例,使用 async/await 來實現:
const fs = require('fs'); const bluebird = require('bluebird'); const readFile = bluebird.promisify(fs.readFile); async function read() { await readFile(A, 'utf-8'); await readFile(B, 'utf-8'); await readFile(C, 'utf-8'); //code } read().then((data) => { //code }).catch(err => { //code });
使用 async/await 實現此需求:讀取A,B,C三個文件內容,都讀取成功後,再輸出最終的結果。
function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, 'utf8', (err, data) => { if(err) reject(err); resolve(data); }); }); } async function readAsync() { let data = await Promise.all([ read(A), read(B), read(C) ]); return data; } readAsync().then(data => { console.log(data); });
因此JS的異步發展史,能夠認爲是從 callback -> promise -> generator -> async/await。async/await 使得異步代碼看起來像同步代碼,異步編程發展的目標就是讓異步邏輯的代碼看起來像同步同樣。
因本人水平有限,文中內容未必百分百正確,若有不對的地方,請給我留言,謝謝。
參考文章:
[2] ES6 Promise
[3] ES6 Generator
[4] ES6 async
[5] JavaScript異步編程
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。https://github.com/YvetteLau/...
推薦關注本人公衆號: