非同步
所謂"非同步",簡單說就是一個任務分成兩段,先執行第一段,然後轉而執行其他任務,等做好了準備,再回過頭執行第二段,比如,有一個任務是讀取檔案進行處理,非同步的執行過程就是下面這樣。這種不連續的執行,就叫做非同步。相應地,連續的執行,就叫做同步。
高階函式
函式作為一等公民,可以作為引數和返回值
可以用於批量生成函式
let toString = Object.prototype.toString;
let isString = function (obj) {
return toString.call(obj) == `[object String]`;
}
let isFunction = function (obj) {
return toString.call(obj) == `[object Function]`;
}
let isType = function (type) {
return function (obj) {
return toString.call(obj) == `[object ${type}]`;
}
}
可以用於需要呼叫多次才執行的函式
let after = function(times,task){
return function(){
if(times--==1){
return task.apply(this,arguments);
}
}
}
let fn = after(3,function(){
console.log(3);});
fn();
非同步程式設計的語法目標,就是怎樣讓它更像同步程式設計,有以下幾種
- 回撥函式實現
- 事件監聽
- 釋出訂閱
- Promise/A+ 和生成器函式
- async/await
回撥
所謂回撥函式,就是把任務的第二段單獨寫在一個函式裡面,等到重新執行這個任務的時候,就直接呼叫這個函式
fs.readFile('某個檔案', function (err, data) {
if (err) throw err;
console.log(data);
});
這是一個錯誤優先的回撥函式(error-first callbacks)
,這也是Node.js
本身的特點之一。
回撥的問題
異常處理
try{
//xxx
}catch(e){//TODO}
非同步程式碼時try catch不再生效
let async = function(callback){
try{
setTimeout(function(){
callback();
},1000)
}catch(e){
console.log('捕獲錯誤',e);
}
}
async(function(){
console.log(t);
});
因為這個回撥函式被存放了起來,直到下一個事件環的時候才會取出,try只能捕獲當前迴圈內的異常,對callback非同步無能為力。
Node
在處理異常有一個約定,將異常作為回撥的第一個實參傳回,如果為空表示沒有出錯。
async(function(err,callback){
if(err){
console.log(err);
}
});
非同步方法也要遵循兩個原則
- 必須在非同步之後呼叫傳入的回撥函式
- 如果出錯了要向回撥函式傳入異常供呼叫者判斷
let async = function(callback){
try{
setTimeout(function(){
if(success)
callback(null);
else
callback('錯誤');
},1000)
}catch(e){
console.log('捕獲錯誤',e);
}
}
回撥地獄
非同步多級依賴的情況下巢狀非常深,程式碼難以閱讀的維護
let fs = require('fs');
fs.readFile('template.txt','utf8',function(err,template){
fs.readFile('data.txt','utf8',function(err,data){
console.log(template+' '+data);
})
})
非同步流程解決方案
事件釋出/訂閱模型
訂閱事件實現了一個事件與多個回撥函式的關聯
let fs = require('fs');
let EventEmitter = require('events');
let eve = new EventEmitter();
let html = {};
eve.on('ready',function(key,value){
html[key] = value;
if(Object.keys(html).length==2){
console.log(html);
}
});
function render(){
fs.readFile('template.txt','utf8',function(err,template){
eve.emit('ready','template',template);
})
fs.readFile('data.txt','utf8',function(err,data){
eve.emit('ready','data',data);
})
}
render();
哨兵變數
let fs = require('fs');
let after = function(times,callback){
let result = {};
return function(key,value){
result[key] = value;
if(Object.keys(result).length==times){
callback(result);
}
}
}
let done = after(2,function(result){
console.log(result);
});
function render(){
fs.readFile('template.txt','utf8',function(err,template){
done('template',template);
})
fs.readFile('data.txt','utf8',function(err,data){
done('data',data);
})
}
rende
Promise/Deferred模式
生成器Generators/ yield
當你在執行一個函式的時候,你可以在某個點暫停函式的執行,並且做一些其他工作,然後再返回這個函式繼續執行, 甚至是攜帶一些新的值,然後繼續執行。
上面描述的場景正是JavaScript生成器函式所致力於解決的問題。當我們呼叫一個生成器函式的時候,它並不會立即執行, 而是需要我們手動的去執行迭代操作(next方法)。也就是說,你呼叫生成器函式,它會返回給你一個迭代器。迭代器會遍歷每個中斷點。next
方法返回值的value
屬性,是Generator
函式向外輸出資料next
方法還可以接受引數,這是向 Generator 函式體內輸入資料
生成器的使用
function* foo () {
var index = 0;
while (index < 2) {
yield index++; //暫停函式執行,並執行yield後的操作
}
}
var bar = foo(); // 返回的其實是一個迭代器
console.log(bar.next()); // { value: 0, done: false }
console.log(bar.next()); // { value: 1, done: false }
console.log(bar.next()); // { value: undefined, done: true }
Coco是一個為Node.js和瀏覽器打造的基於生成器的流程控制工具,藉助於Promise,你可以使用更加優雅的方式編寫非阻塞程式碼。
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);
});
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, reason => reject(reason));
}
}();
});
}
Async/ await
使用async關鍵字,你可以輕鬆地達成之前使用生成器和co函式所做到的工作
Async
的優點
- 內建執行器
- 更好的語義
- 更廣的適用性
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));
async 函式的實現
async
函式的實現,就是將 Generator
函式和自動執行器,包裝在一個函式裡。
async function read() {
let template = await readFile('./template.txt');
let data = await readFile('./data.txt');
return template + '+' + data;
}
// 等同於
function read(){
return co(function*() {
let template = yield readFile('./template.txt');
let data = yield readFile('./data.txt');
return template + '+' + data;
});
}
async_function- generator