本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或從新修改使用,但需註明來源。署名 4.0 國際 (CC BY 4.0)javascript
咱們知道,Node.js中有兩種事件處理方式,分別是callback
(回調)和EventEmitter
(事件發射器)。本文首先介紹的是callback
。前端
error-first callback
錯誤優先是Node.js回調方式的標準。java
第一個參數是error
,後面的參數纔是結果。node
咱們以現實生活中去面試來舉個🌰,面試成功咱們漏出洋溢的笑容,面試失敗咱們就哭並嘗試找到失敗的緣由。es6
try { interview(function() { console.log('smile'); }); } catch(e) { console.log('cry', e); } function interview(callback) { setTimeout(() => { if (Math.random() < 0.1) { callback('success'); } else { throw new Error('fail'); } }, 500); }
如上代碼運行後,try/catch
並不像咱們所想,它並無抓取到錯誤,錯誤反而被拋到了Node.js
全局,致使程序崩潰。(是因爲Node.js的每個事件循環都是一個全新的調用棧Call Stack)面試
爲了解決上面的問題,Node.js官方造成了以下規範:npm
interview(function (res) { if (res) { return console.log('cry'); } console.log('smile'); }) function interview (callback) { setTimeout(() => { if (Math.random() < 0.8) { callback(null, 'success'); } else { callback(new Error('fail')); } }, 500); }
Callback hell
XX大廠有三輪面試,看下面的🌰編程
interview(function (err) { if (err) { return console.log('cry at 1st round'); } interview(function (err) { if (err) { return console.log('cry at 2nd round'); } interview(function (err) { return console.log('cry at 3rd round'); }) console.log('smile'); }) }) function interview (callback) { setTimeout(() => { if (Math.random() < 0.1) { callback(null, 'success'); } else { callback(new Error('fail')); } }, 500); }
咱們再來看併發狀況下callback的表現。redux
同時去兩家公司面試,當兩家面試都成功時咱們纔會開心,看下面這個🌰promise
var count = 0; interview(function (err) { if (err) { return console.log('cry'); } count++; }) interview(function (err) { if (err) { return console.log('cry'); } count++; if (count) { //當count知足必定條件時,面試都經過 //... return console.log('smile'); } }) function interview (callback) { setTimeout(() => { if (Math.random() < 0.1) { callback(null, 'success'); } else { callback(new Error('fail')); } }, 500); }
異步邏輯的增多隨之而來的是嵌套深度的增長。如上的代碼是有不少缺點的:
爲了解決回調地獄,社區曾提出了一些解決方案。
1. async.js npm包,是社區早期提出的解決回調地獄的一種異步流程控制庫。2.thunk 編程範式,著名的
co
模塊在v4之前的版本中曾大量使用Thunk函數。Redux中也有中間件redux-thunk
。
不過它們都退出了歷史舞臺。
畢竟軟件工程沒有銀彈,取代他們的方案是Promise
Promise/A+規範鎮樓,ES6採用的這個規範實現的Promise。
Promise 是異步編程的一種解決方案,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
簡單說,Promise就是當前事件循環不會獲得結果,但將來的事件循環會給到你結果。
毫無疑問,Promise是一個渣男。
Promise也是一個狀態機,只能從pending
變爲如下狀態(一旦改變就不能再變動)
// nodejs 不會打印狀態 // Chrome控制檯中能夠 var promise = new Promise(function(resolve, reject){ setTimeout(() => { resolve(); }, 500) }) console.log(promise); setTimeout(() => { console.log(promise); }, 800); // node.js中 // promise { <pending> } // promise { <undefined> } // 將上面代碼放入閉包中扔到google控制檯裏 // google中 // Promise { <pending> } // Promise { <resolved>: undefined }
Promise
resolved
狀態的Promise會回調後面的第一個.then
rejected
狀態的Promise會回調後面的第一個.catch
任何一個
rejected
狀態且後面沒有.catch
的Promise,都會形成瀏覽器/node環境
的全局錯誤。
Promise比callback優秀的地方,是能夠解決異步流程控制問題。
(function(){ var promise = interview(); promise .then((res) => { console.log('smile'); }) .catch((err) => { console.log('cry'); }); function interview() { return new Promise((resoleve ,reject) => { setTimeout(() => { if (Math.random() > 0.2) { resolve('success'); } else { reject(new Error('fail')); } }, 500); }); } })();
執行then
和catch
會返回一個新的Promise,該Promise最終狀態根據then
和catch
的回調函數的執行結果決定。咱們能夠看下面的代碼和打印出的結果:
(function(){ var promise = interview(); var promise2 = promise .then((res) => { throw new Error('refuse'); }); setTimeout(() => { console.log(promise); console.log(promise2); }, 800); function interview() { return new Promise((resoleve ,reject) => { setTimeout(() => { if (Math.random() > 0.2) { resolve('success'); } else { reject(new Error('fail')); } }, 500); }); } })(); // Promise { <resolved>: "success"} // Promise { <rejected>: Error:refuse }
若是回調函數最終是throw
,該Promise是rejected
狀態。若是回調函數最終是
return
,該Promise是resolved
狀態。但若是回調函數最終
return
了一個Promise,該Promise會和回調函數return Promise
狀態保持一致。
咱們來用Promise從新實現一下上面去大廠三輪面試代碼。
(function() { var promise = interview(1) .then(() => { return interview(2); }) .then(() => { return interview(3); }) .then(() => { console.log('smile'); }) .catch((err) => { console.log('cry at' + err.round + 'round'); }); function interview (round) { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.2) { resolve('success'); } else { var Error = new Error('fail'); error.round = round; reject(error); } }, 500); }); } })();
與回調地獄相比,Promise實現的代碼通透了許多。
Promise在必定程度上把回調地獄變成了比較線性的代碼,去掉了橫向擴展,回調函數放到了then中,但其仍然存在於主流程上,與咱們大腦順序線性的思惟邏輯仍是有出入的。
(function() { Promise.all([ interview('Alibaba'), interview('Tencent') ]) .then(() => { console.log('smile'); }) .catch((err) => { console.log('cry for' + err.name); }); function interview (name) { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.2) { resolve('success'); } else { var Error = new Error('fail'); error.name = name; reject(error); } }, 500); }); } })();
上面代碼中的catch
是存在問題的。注意,它只能獲取第一個錯誤。
Generator和Generator Function是ES6中引入的新特性,是在Python、C#等語言中借鑑過來。
生成器的本質是一種特殊的迭代器。
function * doSomething() {}
如上所示,函數後面帶「*」的就是Generator。
function * doSomething() { interview(1); yield; // Line (A) interview(2); } var person = doSomething(); person.next(); // 執行interview1,第一次面試,而後懸停在Line(A)處 person.next(); // 恢復Line(A)點的執行,執行interview2,進行第二次次面試
next的返回結果
第一個person.next()返回結果是{value:'', done:false}第二個person.next()返回結果是{value:'', done:true}
關於next的返回結果,咱們要知道,若是done
的值爲true
,即表明Generator裏的異步操做所有執行完畢。
爲了能夠在Generator中使用多個yield,TJ Holowaychuk
編寫了co
這個著名的ES6模塊。co的源碼有不少巧妙的實現,你們能夠自行閱讀。
Generator的弊端是沒有執行器,它自己是爲了計算而設計的迭代器,並非爲了流程控制而生。co的出現較好的解決了這個問題,可是爲何咱們非要藉助於co而不直接實現呢?
async/await
被選爲天之驕子應運而生。
async function
是一個穿越事件循環存在的function。
async function
其實是Promise的語法糖封裝。它也被稱爲異步編程的終極方案-以同步的方式寫異步。
await
關鍵字能夠"暫停"async function
的執行。
await
關鍵字能夠以同步的寫法獲取Promise的執行結果。
try/catch
能夠獲取await
所獲得的任意錯誤,解決了上面Promise中catch只能獲取第一個錯誤的問題。
(async function () { try { await interview(1); await interview(2); await interview(3); } catch (e) { return console.log('cry at' + e.round); } console.log('smile'); })();
(async function () { try { await Promise.all([interview(1), interview(2)]); } catch (e) { return console.log('cry at' + e.round); } console.log('smile'); })();
不管是相比callback
,仍是Promise
,async/await
只用短短几行代碼便實現了異步流程控制。
遺憾的是,async/await
最終沒能進入ES7
規範(只能等到ES8
),但在Chrome V8
引擎裏得以實現,Node.js v7.6
也集成了async
函數。
在常見的Web應用中,在DAO層使用Promise較好,在Service層使用async函數較好。
參考:
1.看到這裏了就點個贊支持下吧,你的點贊是我創做的動力。
2.關注公衆號前端食堂
,你的前端食堂,記得按時吃飯!
3.入冬了,多穿衣服不要着涼~!