目前有幾個比較好的解決方法前端
fs.readFile('./sample.txt', 'utf-8', (err, content) => {
let keyword = content.substring(0, 5);
db.find(`select * from sample where kw = ${keyword}`, (err, res) => {
get(`/sampleget?count=${res.length}`, data => {
console.log(data);
});
});
});
複製代碼
以上代碼包括了三個異步操做:node
咱們每增長一個異步請求,就會多添加一層回調函數的嵌套,這樣下去,可讀性會愈來愈低,也不易於之後的代碼維護。過多的回調也就讓咱們陷入「回調地獄」。接下來會大概介紹一下規避回調地獄的方法。es6
回調嵌套所帶來的一個重要的問題就是代碼不易閱讀與維護。由於廣泛來講,過多的嵌套(縮進)會極大的影響代碼的可讀性。基於這一點,能夠進行一個最簡單的優化----將各個步驟拆解爲單個functionweb
//HTTP請求
function getData(count) {
get(`/sampleget?count=${count}`, data => {
console.log(data);
});
}
//查詢數據庫
function queryDB(kw) {
db.find(`select * from sample where kw = ${kw}`, (err, res) => {
getData(res.length);
});
}
//讀取文件
function readFile(filepath) {
fs.readFile(filepath, 'utf-8', (err, content) => {
let keyword = content.substring(0, 5);
queryDB(keyword);
});
}
//執行函數
readFile('./sample.txt');
複製代碼
經過改寫,再加上註釋,能夠很清晰的知道這段代碼要作的事情。該方法很是簡單,具備必定的效果,可是缺乏通用性。數據庫
addEventListener應該不陌生吧,若是你在瀏覽器中寫過監聽事件。 借鑑這個思路,咱們能夠監聽某一件事情,當事情發生的時候,進行相應的回調操做;另外一方面,當某些操做完成後,經過發佈事件觸發回調。這樣就能夠將本來捆綁在一塊兒的代碼解耦。編程
const events = require('events');
const eventEmitter = new events.EventEmitter();
eventEmitter.on('db', (err, kw) => {
db.find(`select * from sample where kw = ${kw}`, (err, res) => {
eventEmitter('get', res.length);
});
});
eventEmitter.on('get', (err, count) => {
get(`/sampleget?count=${count}`, data => {
console.log(data);
});
});
fs.readFile('./sample.txt', 'utf-8', (err, content) => {
let keyword = content.substring(0, 5);
eventEmitter. emit('db', keyword);
});
複製代碼
events 模塊是node原生模塊,用node實現這種模式只須要一個事件發佈/監聽的庫。小程序
Promise是es6的規範 首先,咱們須要將異步方法改寫成Promise,對於符合node規範的回調函數(第一個參數必須是Error), 可使用bluebird的promisify方法。該方法接受一個標準的異步方法並返回一個Promise對象微信小程序
const bluebird = require('bluebird');
const fs = require("fs");
const readFile = bluebird.promisify(fs.readFile);
複製代碼
這樣fs.readFile就變成一個Promise對象。 可是可能有些異步沒法進行轉換,這樣咱們就須要使用原生Promise改造。 以fs.readFile爲例,藉助原生Promise來改造該方法:promise
const readFile = function (filepath) {
let resolve,
reject;
let promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
let deferred = {
resolve,
reject,
promise
};
fs.readFile(filepath, 'utf-8', function (err, ...args) {
if (err) {
deferred.reject(err);
}
else {
deferred.resolve(...args);
}
});
return deferred.promise;
}
複製代碼
咱們在方法中建立一個Promise對象,並在異步回調中根據不一樣的狀況使用reject與resolve來改變Promise對象的狀態。該方法返回這個Promise對象。其餘的一些異步方法能夠參照這種方式進行改造。 假設經過改造,readFile、queryDB與getData方法均會返回一個Promise對象。代碼就會變成這樣:瀏覽器
readFile('./sample.txt').then(content => {
let keyword = content.substring(0, 5);
return queryDB(keyword);
}).then(res => {
return getData(res.length);
}).then(data => {
console.log(data);
}).catch(err => {
console.warn(err);
});
複製代碼
經過then的鏈式改造。使代碼的整潔度在必定的程度上有了一個較大的提升。
generator是es6中的一個新的語法。在function關鍵字後添加*便可將函數變爲generator。
const gen = function* () {
yield 1;
yield 2;
return 3;
}
複製代碼
執行generator將會返回一個遍歷器對象,用於遍歷generator內部的狀態。
let g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: true }
g.next(); // { value: undefined, done: true }
複製代碼
能夠看到,generator函數有一個最大的特色,能夠在內部執行的過程當中交出程序的控制權,yield至關於起到了一個暫停的做用;而當必定的狀況下,外部又將控制權再移交回來。 咱們用generator來封裝代碼,在異步任務處使用yield關鍵詞,此時generator會將程序執行權交給其餘代碼,而在異步任務完成後,調用next方法來恢復yield下方代碼的執行。以readFile爲例,大體流程以下:
// 咱們的主任務——顯示關鍵字
// 使用yield暫時中斷下方代碼執行
// yield後面爲promise對象
const showKeyword = function* (filepath) {
console.log('開始讀取');
let keyword = yield readFile(filepath);
console.log(`關鍵字爲${filepath}`);
}
// generator的流程控制
let gen = showKeyword();
let res = gen.next();
res.value.then(res => gen.next(res));
複製代碼
能夠看到,上面的方法雖然都在必定程度上解決了異步編程中回調帶來的問題。然而
所以,這裏在介紹一個方法,它就是es7中的async/await。 簡單介紹一下async/await。基本上,任何一個函數均可以成爲async函數,如下都是合法的書寫形式
async function foo () {};
const foo = async function () {};
const foo = async () => {};
複製代碼
未完待續——