本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或從新修改使用,但需註明來源。署名 4.0 國際 (CC BY 4.0)前端
咱們知道,Node.js中有兩種事件處理方式,分別是callback
(回調)和EventEmitter
(事件發射器)。本文首先介紹的是callback
。node
error-first callback
錯誤優先是Node.js回調方式的標準。es6
第一個參數是error
,後面的參數纔是結果。web
咱們以現實生活中去面試來舉個🌰,面試成功咱們漏出洋溢的笑容,面試失敗咱們就哭並嘗試找到失敗的緣由。面試
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)npm
爲了解決上面的問題,Node.js官方造成了以下規範:編程
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大廠有三輪面試,看下面的🌰redux
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的表現。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.入冬了,多穿衣服不要着涼~!