js中異步的發(fā)展跌跌撞撞走了十年菩暗,從開始的回調惡魔發(fā)展到現(xiàn)在的仿佛同步的寫法揪胃。再次回顧署海,特此總結。
先簡述一下我們遇到的問題:
當我們訪問一個頁面的時候肄方,我們可能需要讀取兩個文件冰垄,一個是模板文件,一個是這個頁面的數(shù)據(jù)文件权她。引為讀取文件都比較耗時虹茶,我們通過異步來處理。
let fs = require('fs');
fs.readFile('./template.txt', 'utf8', function (err, template) {
fs.readFile('./data.txt', 'utf8', function (err, data) {
console.log(template + ' ' + data);
})
})
看上去好像沒有什么問題隅要,兩層的嵌套好像還能接受蝴罪,但是如果請求data.txt后有需要其他文件的數(shù)據(jù),這樣層層嵌套下去會出現(xiàn)什么現(xiàn)象呢步清?
fs.readFile('./template.txt', 'utf8', function (err, template) {
fs.readFile('./data.txt', 'utf8', function (err, data) {
fs.readFile('./data2.txt', 'utf8', function (err, data1) {
fs.readFile('./data3.txt', 'utf8', function (err, data2) {
fs.readFile('./data4.txt', 'utf8', function (err, data3) {
console.log(template + ' ' + data + ' ' + data1 + ' ' + data2 + ' ' + data3);
})
})
})
})
})
如果這是被人的代碼要门,你要改其中的一部分虏肾。什么感受?
這就是傳說中的回調金字塔欢搜,又叫回調地獄封豪。它使js代碼變的非常難看和難以維護并且效率低下。
如何解決這個問題炒瘟?
事件發(fā)布訂閱
我們可以通過node核心模塊中的 EventEmitter 類來實現(xiàn)吹埠,通過它可以創(chuàng)建事件發(fā)射器的實例,里面有兩個核心方法疮装,一個叫on缘琅,一個叫emit。on表示注冊監(jiān)聽事件廓推,emit表示發(fā)射事件胯杭。
let fs = require('fs');
let EventEmitter = require('events');
let eve = new EventEmitter();
let html = {};//存放最終數(shù)據(jù)
//監(jiān)聽數(shù)據(jù)獲取成功事件,當事件發(fā)生之后調用回調函數(shù)
eve.on('ready', function (key, value) {
html[key] = value;
if (Object.keys(html).length == 2) {
console.log(html);//在此能夠獲取到兩個文件的數(shù)據(jù)受啥。
}
});
fs.readFile('./template.txt', 'utf8', function (err, template) {
//‘ready’為事件名 做个,往后是傳遞給回調函數(shù)的參數(shù)
eve.emit('ready', 'template', template);
})
fs.readFile('./data.txt', 'utf8', function (err, data) {
eve.emit('ready', 'data', data);
});
請求template.txt和data.txt文件數(shù)據(jù),當成功后發(fā)布ready事件滚局。on處訂閱了ready事件居暖,當他觸發(fā)的時候,on方法執(zhí)行藤肢。
有點兒類似我們的綁定事件比如:onClick太闺,給一個元素綁定onClick事件,當頁面觸發(fā)的時候再執(zhí)行嘁圈。
哨兵變量
let fs = require('fs');
function render(length, cb) {
let html = {};
return function (key, value) {
html[key] = value;
if (Object.keys(html).length == length) {
cb(html);
}
}
}
//2是一個哨兵變量省骂,將讀取文件數(shù)據(jù)成功后執(zhí)行的方法作為回調函數(shù)傳給render方法。
let done = render(2, function (html) {
console.log(html);
});
fs.readFile('./template.txt', 'utf8', function (err, template) {
done('template', template);
})
fs.readFile('./data.txt', 'utf8', function (err, data) {
done('data', data);
})
不使用回調嵌套遇到的問題是:不知道讀取文件的函數(shù)什么時候執(zhí)行完最住。因為只有全部執(zhí)行完后才能執(zhí)行需要文件數(shù)據(jù)的方法钞澳。
上面代碼render方法執(zhí)行時傳入的2相當于一個哨兵變量,可以是2涨缚,可以是3轧粟,取決于后面需要讀取幾個文件的數(shù)據(jù)。將需要讀取的文件數(shù)量脓魏,和讀取全部文件成功后的方法作為回調函數(shù)傳入render兰吟。done為render執(zhí)行后返回的函數(shù)。
每次獲取文件成功后執(zhí)行done方法即可茂翔。
通過這種方法也可以獲取到最終全部文件的數(shù)據(jù)混蔼。
Promise/Deferred模式
let fs = require('fs');
function readFile(filename) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, function (err, data) {
if (err)
reject(err);
else
resolve(data);
})
})
}
function *read() {
let template = yield readFile('./template.txt');
let data = yield readFile('./data.txt');
return template + '+' + data;
}
co(read).then(function (data) {
console.log(data);
}, function (err) {
console.log(err);
});
Async/ await
let fs = require('fs');
function readFile(filename) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, 'utf8', function (err, data) {
if (err)
reject(err);
else
resolve(data);
})
})
}
async function read() {
let template = await readFile('./template.txt');
let data = await readFile('./data.txt');
return template + '+' + data;
}
let result = read();
result.then(data=>console.log(data));