在咱們平常編碼中,須要異步的場景不少,好比讀取文件內容、獲取遠程數據、發送數據到服務端等。由於瀏覽器環境裏Javascript
是單線程的,因此異步編程在前端領域尤其重要。前端
所謂異步,是指當一個過程調用發出後,調用者不能馬上獲得結果。實際處理這個調用的過程在完成後,經過狀態、通知或者回調來通知調用者。git
好比咱們寫這篇文字時點擊發布按鈕,咱們並不能立刻獲得文章發佈成功或者失敗。等待服務器處理,這段時間咱們能夠作其餘的事情,當服務器處理完成後,通知咱們是否發佈成功。es6
所謂同步,是指當一個過程調用發出後,必須等待這個過程處理完成後,再處理其餘事情。即堵塞執行。github
在es6
以前,咱們實現異步有4種方法,回調、事件、發佈訂閱和promise方式。web
function dealTask(param, callback) { // Deal with some time consuming tasks. // ... Object.prototype.toString.call(callback) === '[object Function]' ? callback() : null; } dealTask({ id: 1 }, function() { console.log('... I am in the callback...'); })
回調的方式來實現異步其實就是把須要在當前任務完成後執行的函數當成參數傳入,完成任務後執行便可。編程
function dealTask(param) { // Deal with some time consuming tasks. // ... events.trigger('dealTaskFinish') } events.on('dealTaskFinish', function() { console.log('...I am in the end...'); })
經過事件來實現回調,好處是方便實用,跨模塊傳遞數據。壞處是,事件用的多了後業務邏輯混亂,不知道哪裏註冊過哪裏監聽過。api
另外須要注意的是在web component
場景下,mount
後註冊過的事件須要在unmount
釋放,否則會致使內存泄露。promise
發佈訂閱的簡單例子是,一個開關,同時並聯幾個燈泡(在不一樣房間),觸發的時候,幾個燈泡都會獲得指令,而後執行發光的行爲。瀏覽器
// 使用pubsubz實現 var testSubscriber = function(data ){ console.log(data ); }; var testSubscription = pubsubz.subscribe( 'example', testSubscriber ); pubsubz.publish( 'example', 'hello' );
訂閱發佈與的性質與"事件監聽"相似,不一樣的是,咱們能夠經過查看"消息中心",瞭解存在多少信號、每一個信號有多少訂閱者,從而監控程序的運行。服務器
function helloWorld (ready) { return new Promise(function (resolve, reject) { if (ready) { resolve("Hello World!") } else { reject("Good bye!") } }) } helloWorld(true).then(function (message) { console.log(message) }, function (error) { console.log(error) })
Promises對象是CommonJS工做組提出的一種規範,是對異步編程的一種統一,其實也就是語法糖,可閱讀性變強了而已。
在ES6出來之後,咱們的異步方式也發生了改變。
Generator函數是協程在ES6的實現,最大特色就是能夠交出函數的執行權(即暫停執行)。
Generator函數能夠暫停執行和恢復執行,這是它能封裝異步任務的根本緣由。除此以外,它還有兩個特性,使它能夠做爲異步編程的完整解決方案:函數體內外的數據交換和錯誤處理機制。
function* Foo(x) { yield x + 1; var y = yield null; return x + y; } var foo = Foo(5); foo.next(); // { value: 6, done: false } foo.next(); // { value: null, done: false } foo.next(8); // { value: 13, done: true }
next方法返回值的value屬性,是Generator函數向外輸出數據;next方法還能夠接受參數,這是向Generator函數體內輸入數據。
yield命令用於將程序的執行權移出Generator函數,那麼就須要一種方法,將執行權再交還給Generator函數
上面的方式,是咱們手動調用Generator函數執行,可是當咱們的須要執行next方法不少時,就須要Generator函數自動執行了。
Generator函數自動執行的意思是,經過必定的方法來自動執行next方法,好比:
function autoRunGen(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); }
co模塊是TJ開發的一個小工具,用於Generator函數的自動執行。他的主要思想和上面的代碼片斷相似。
使用co的前提條件是,Generator函數的yield命令後面,只能是Promise對象。
co(function *() { var data = yield $.get('/api/data'); console.log(data); var user = yield $.get('/api/user'); console.log(user); var products = yield $.get('/api/products'); console.log(products); });
co模塊使得咱們能夠像寫同步代碼同樣,寫異步代碼。
async函數僅僅是Generator函數的語法糖。
var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) reject(error); resolve(data); }); }); }; var gen = function* (){ var f1 = yield readFile('/etc/a.js'); var f2 = yield readFile('/etc/b.js'); console.log(f1.toString()); console.log(f2.toString()); };
使用async/await
方式:
var asyncReadFile = async function (){ var f1 = await readFile('/etc/a.js'); var f2 = await readFile('/etc/b.js'); console.log(f1.toString()); console.log(f2.toString()); };
async函數就是將Generator函數的星號(*)替換成async,將yield替換成await,僅此而已。
不一樣的是,Generator執行須要手動執行,而async函數能夠自動執行,像寫同步同樣寫異步。Generator返回Iterator對象,async函數返回Promise對象。