JavaScript 的所有網路操作,瀏覽器事件,都必須是非同步執行。 正如我們所知道的那樣,在JavaScript中,非同步程式設計方式只能通過JavaScript語言中的一等公民函式才能完成:這種方式意味著我們可以將一個函式作為另一個函式的引數,在這個函式的內部可以呼叫被傳遞進來的函式(即回撥函式)。
let fs = require('fs');
fs.readFile('./1.txt','utf8',function(err,data){
if(err){//如果err有值,就表示程式出錯了
console.log(err);
}else{//如果error為空,就表示 成功了,沒有錯誤
console.log(data);
}
})
複製程式碼
回撥函式的問題
-
- 無法捕獲錯誤 try catch
-
- 不能return
-
- 回撥地獄
function read(filename){
fs.readFile(filename,'utf8',function(err,data){
throw Error('出錯了')
if(err){//如果err有值,就表示程式出錯了
console.log(err);
}else{//如果error為空,就表示 成功了,沒有錯誤
console.log(data);
}
})
}
try{
read('./1.txt');
}catch(e){
console.log('err',e);
};
複製程式碼
當你訪問伺服器的時候,比如要請求一個HTML頁面,比如是使用者列表。伺服器一方面會去讀取讀模板檔案,可能是ejs pug jade handlebar ,另外一方面還要讀取資料(可能會放在檔案裡,也可以會放在資料裡),它們都很慢,所以都是非同步的。
這種惡魔金字塔有以下問題
-
- 非常難看
-
- 非常難以維護
-
- 效率比較低,因為它們是序列的
fs.readFile('./template.txt', 'utf8', function (err, template) {
fs.readFile('./data.txt', 'utf8', function (err, data) {
console.log(template + ' ' + data);
})
})
複製程式碼
如何解決這個回撥巢狀的問題
1.通過事件釋出訂閱來實現 這是node核心模組中的一個類,通過它可以建立事件發射器的例項,裡面有兩個核心方法,一個叫on emit,on表示註冊監聽,emit表示發射事件
let EventEmitter = require('events');
let eve = new EventEmitter();
//這個html物件是存放最終資料
let html = {};//template data
//監聽資料獲取成功事件,當事件發生之後呼叫回撥函式
eve.on('ready',function(key,value){
html[key] = value;
if(Object.keys(html).length == 2){
console.log(html);
}
});
fs.readFile('./template.txt', 'utf8', function (err, template) {
//1事件名 2引數往後是傳遞給回撥函式的引數
eve.emit('ready','template',template);
})
fs.readFile('./data.txt', 'utf8', function (err, data) {
eve.emit('ready','data',data);
})*/
//通過一個哨兵函式來處理
function done(key,value){
html[key] = value;
if(Object.keys(html).length == 2){
console.log(html);
}
}
複製程式碼
ES6的很多特性都跟Generator扯上關係,而且實際用處比較廣, 包含了任何需要非同步的模組, 比如ajax, filesystem, 或者陣列物件遍歷等都可以用到; Generator函式和普通的函式區別有兩個, 1:function和函式名之間有一個*號, 2:函式體內部使用了yield表示式;比如這樣:
/**
* 生成器是一個函式,可以用來生成迭代器
* 生成器函式和普通函式不一樣,普通函式是一旦呼叫一定會執行完
* 但是生成器函式中間可以暫停,可以執行一會歇一會
*/
//生成器函式有一個特點,需要加個*
//生成器有若干個階段,如何劃分這些階段呢?
function *go(a){
console.log(1);
//此處的b用來供外界輸入進來的
//這一行實現輸入和輸出,本次的輸出放在yield後面,下次的輸入放在yield前面
let b = yield a;
console.log(2);
let c = yield b;
console.log(3);
return c;
}
//生成器函式和普通的函式不一樣,呼叫它的話函式並不會立刻執行
//它會返回此生成器的迭代器,迭代器是一個物件,每呼叫一次next就可以返回一個值物件
let it = go("a值");
//next第一次執行不需要引數,傳引數沒有意義
let r1 = it.next();
//第一次呼叫next會返回一個物件,此物件有兩個屬性,一個是value就是yield後面那個值,一個是done表示是否迭代完成
console.log(r1);//{ value: 'a', done: false }
let r2 = it.next('B值');
console.log(r2);//{ value: 'B值', done: false }
let r3 = it.next('C值');
console.log(r3);//{ value: 'C值', done: true }
複製程式碼
!重點來了
先回憶之前promise對非同步的實現,以 bluebird為例:
let Promise = require('bluebird');
let fs = require('fs');
function promisifyAll(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key) && typeof obj[key] == 'function') {
obj[key+'Async'] = Promise.promisify(obj[key]);
}
}
}
//它會遍歷物件上面的所有方法,然後對每個方法新增一個新的方法 Async
promisifyAll(fs);
fs.readFileAsync('./1.txt', 'utf8').then(data => console.log(data));
複製程式碼
現在將Generator與promise綜合在一起:
let fs = require('fs');
function readFile(filename) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, 'utf8', function (err, data) {
err ? reject(err) : resolve(data);
});
})
}
function *read() {
console.log('開始');
let a = yield readFile('1.txt');
console.log(a);
let b = yield readFile('2.txt');
console.log(b);
let c = yield readFile('3.txt');
console.log(c);
return c;
}
function co(gen) {
let it = gen();//我們要讓我們的生成器持續執行
return new Promise(function (resolve, reject) {
!function next(lastVal) {
let {value,done} = it.next(lastVal);
if(done){
resolve(value);
}else{
value.then(next,reject);
}
}()
});
}
co(read).then(function (data) {
console.log(data);
});
複製程式碼
隨著 Node 7的釋出,越來越多的人開始研究據說是非同步程式設計終級解決方案的 async/await
let Promise = require('bluebird');
let readFile = Promise.promisify(require('fs').readFile);
async function read() {
//await後面必須跟一個promise,
let a = await readFile('./1.txt','utf8');
console.log(a);
let b = await readFile('./2.txt','utf8');
console.log(b);
let c = await readFile('./3.txt','utf8');
console.log(c);
return 'ok';
}
read().then(data => {
console.log(data);
});
複製程式碼
async await是語法糖,內部還是用generator+promise實現