非同步方案進化過程: callback ---> promise ---> generator + co ---> async + await 本文以nodejs中最常用的檔案非同步讀取操作readFile為例,來說明各個方案下的實現方式。
前提
假設我們現在有兩個檔案1.txt和2.txt,1.txt的內容是'./2.txt'(2的路徑),2.txt的內容是'非同步,又見非同步'。
需求1
拿到1的內容作為路徑去讀取2的內容列印出來。
需求2
同時讀取兩個檔案,在兩個檔案都讀取完成時,將檔案中的內容合併。
callback
需求1實現
fs.readFileSync('./1.txt', 'utf8', function(err, data) {
if(err) { throw err; }
fs.readFileSync(data, 'utf8', function(err, data) {
if(err) { throw err; }
console.log(data);
})
})
複製程式碼
此時我們很容易利用了回撥巢狀,巢狀多了就容易累積出回撥地獄。
需求2實現
這個需求實現我們先需要了解一個lodash的_after函式
_after函式
_after函式返回一個函式,在這個函式呼叫次數達到一定限度時,呼叫回撥函式。下面寫一個自己的after函式:
function after(times, callback) {
return function() {
if(--times === 0) {
callback();
}
};
}
let sleep = after(3, function() {
console.log('睡飽了');
});
sleep();
sleep();
sleep();//列印出睡飽了
複製程式碼
寫一個afterRead函式來得到一個read函式,如下:
function afterRead(times, callbacks) {
let arr = [];
return function(path) {
fs.readFile(path, 'utf8', function(err, data) {
if(err) { throw err; }
arr.push(data);
if(--times === 0) {
callbacks(arr);
}
});
}
}
let read = afterRead(2, function(dataArr) {
console.log(dataArr);
});
read('./1.txt');
read('./2.txt');//讀完第二個檔案列印出['./2.txt', '非同步,又見非同步']
複製程式碼
Promise
需求1實現
function read(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf8', function(err, data) {
if(err) { reject(err); }
resolve(data);
});
});
}
read('./1.txt').then(function(data) {
return read(data);//將產生的promise返回出去進行下一步處理
}, function(err) {
console.log(err);
}).then(function(data) {
console.log(data);
});
複製程式碼
需求2實現
使用Promise.all(),能夠方便進行處理,並且返回的資料是按照請求發起的順序存放
Promise.all([read('./1.txt'), read('./2.txt')]).then(function(data) {
console.log(data);
});
複製程式碼
Generator + co
Generator是一個能夠返回迭代器的函式,需要在function關鍵字和名字之間加上*以標識是個Generator,需要配合yield使用,方法每次執行到yield就會暫停,等待返回的迭代器呼叫next()方法繼續往下執行。
next方法
返回一個包含value和done屬性的物件,value表示值,done布林型別,表示迭代是否完成。
co
co庫可以自動的將generator進行迭代,同時每次迭代都會返回一個promise
模仿co庫的功能
function co(it) {
let task = it();//taskDef生成一個任務迭代器
return new Promise(function(resolve, reject) {
function step(data) {
let {done, value} = task.next(data);
if(!done) {
value.then(function(data) {
step(data);
}, reject);
} else {
resolve(value);
}
}
step();
});
}
複製程式碼
需求1實現
function *readTask() {
let path = yield read('./1.txt');
let content = yield read(path);
return content;
}
co(readTask).then(function(data) {
console.log(data);
});
複製程式碼
終極方案 asyn await
async和await(語法糖) === co + generator
用async 來修飾函式,aysnc需要配await,await只能promise
需求1實現
async function readTask() {
try {
let path = await read('./1.txt');
let content = await read(path);
return content;
} catch (error) {
throw err;
}
}
readTask().then(function(data){
console.log(data);
});
複製程式碼