1. javascript 异步编程
1.1. 回调函数
var result = function() {
setTimeout(() => {
return 5;
}, 1000);
};
console.log(result()); // undefined
用回调函数处理怎么弄呢?让 result 的参数为一个回调函数就可以了,于是代码变成下面这样
var result = function(callback) {
setTimeout(() => {
callback(5);
}, 1000);
};
result(console.log);
现在我们用一个真实的 io 调用替代抢红包,新建一个 numbers.txt,在里面写若干个红包金额,代码如下:
const fs = require('fs');
const readFileAsArray = function(file, cb) {
fs.readFile(file, (err, data) => {
if (err) return cb(err);
const lines = data
.toString()
.trim()
.split('\n');
cb(null, lines);
});
};
readFileAsArray('./numbers.txt', (err, lines) => {
if (err) throw err;
const numbers = lines.map(Number);
console.log(`分别抢到了${numbers}块红包`);
});
定义了一个 readFileAsArray 函数,传两个参:文件名和回调函数,然后调用这个函数,把回调函数写入第二个参数里,就可以控制代码执行顺序了。不过,回调的缺点就是写多了,层层嵌套,又会造成回调地狱的坑爹情况,代码变得难以维护和阅读。
1.2. Promise
Promise 实现了控制反转。原来这个顺序的控制是在代码那边而不是程序员控制,现在有了 Promise,控制权就由人来掌握了,通过一系列 Promise 的方法如 then/catch/all/race 等控制异步流程。Promise 文档
function doAsyncWork() {
// perform async calls
if (success) resolve(data);
else reject(reason);
}
let p: Promise<string> = new Promise(doAsyncWork);
// combine
let p: Promise<string> = new Promise((resolve, reject) => {
// perform async calls
setTimeout(() => {
let foundBooks: string[] = util.GetBookTitlesByCategory(cat);
if (foundBooks.length > 0) {
resolve(foundBooks);
} else {
reject('No books found for that category.');
}
}, 2000);
});
// handling promise result
let p: Promise<string> = MethodThatReturnsPromise();
p
.then(
(titles) => {
console.log(`Found titles: ${titles}`);
throw 'something bad happened';
return titles.length;
},
(reason) => {
return 0;
},
)
.then((numOfBooks) => console.log(`Number of books found: ${numOfBooks}`))
.catch((reason) => console.log(`Error: ${reason}`));
const fs = require('fs');
const readFileAsArray = function(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
const lines = data.toString().split('\n');
resolve(lines);
});
});
};
readFileAsArray('./numbers.txt')
.then((lines) => {
const numbers = lines.map(Number);
console.log(`分别抢到了${numbers}块红包`);
})
.catch((error) => console.error(error));
在这里已经把控制权交给了程序员,代码也变得更好理解。虽然 Promise 有 单值/不可取消 等缺点,不过在现在大部分的情况下实现异步还是够用的。
1.3. await/async
有没有简化的办法呢?ES7 推出了一个语法糖:await/async,它的内部封装了 Promise 和 Generator 的组合使用方式
async function doAsyncWork() {
let results = await getDataFromServer(); // getDataFromServer returns promise
console.log(results);
}
console.log('Calling server to retrieve data...');
doAsyncWork();
console.log('Results will be displayed when ready...');
const fs = require('fs');
const readFileAsArray = function(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
const lines = data.toString().split('\n');
resolve(lines);
});
});
};
async function result() {
try {
const lines = await readFileAsArray('./numbers.txt');
const numbers = lines.map(Number);
console.log(`分别抢到了${numbers}块红包`);
} catch (err) {
console.log('await出错!');
console.log(err);
}
}
result();
这样做的结果是不是让代码可读性更高了!而且也屏蔽了 Promise 和 Generator 的细节。
1.4. event
另一个实现异步的方式是 event,回调(promise、await/async)和 event 的关系就像计划经济和市场经济一样,一个是人为的强制性的控制,一个是根据需求和供给这只看不见的手控制。
const EventEmitter = require('events');
const fs = require('fs');
class MyEventEmitter extends EventEmitter {
executeAsync(asyncFunc, args) {
this.emit('开始');
console.time('执行耗时');
asyncFunc(args, (err, data) => {
if (err) return this.emit('error', err);
this.emit('data', data);
console.timeEnd('执行耗时');
this.emit('结束');
});
}
}
const myEventEmitter = new MyEventEmitter();
myEventEmitter.on('开始', () => {
console.log('开始执行了');
});
myEventEmitter.on('data', (data) => {
console.log(`分别抢到了${data}块红包`);
});
myEventEmitter.on('结束', () => {
console.log('结束执行了');
});
myEventEmitter.on('error', (err) => {
console.error(err);
});
myEventEmitter.executeAsync(fs.readFile, './numbers.txt');
这种事件驱动非常灵活,也不刻意去控制代码的顺序,一旦有事件的供给(emit),它就会立刻消费事件(on),不过正是因为这样,它的缺点也很明显:让程序的执行流程很不清晰。
1.4.1. event + promise + await/async
结合 event 和 promise 的写法:
const EventEmitter = require('events');
const fs = require('fs');
class MyEventEmitter extends EventEmitter {
async executeAsync(asyncFunc, args) {
this.emit('开始');
try {
console.time('执行耗时');
const data = await asyncFunc(args);
this.emit('data', data);
console.timeEnd('执行耗时');
this.emit('结束');
} catch (err) {
console.log('出错了!');
this.emit('error', err);
}
}
}
const readFileAsArray = function(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
const lines = data.toString().split('\r\n');
resolve(lines);
});
});
};
const myEventEmitter = new MyEventEmitter();
myEventEmitter.on('开始', () => {
console.log('开始执行了');
});
myEventEmitter.on('data', (data) => {
console.log(`分别抢到了${data}块红包`);
});
myEventEmitter.on('结束', () => {
console.log('结束执行了');
});
myEventEmitter.on('error', (err) => {
console.error(err);
});
myEventEmitter.executeAsync(readFileAsArray, './numbers.txt');
这种结合的方式基本上可以应付现今的异步场景了,缺点嘛。。。就是代码量比较多
1.4.2. rxjs
简单介绍下 rxjs 和异步的关系:它可以把数据转化成一股流,无论这个数据是同步得到的还是异步得到的,是单值还是多值。
Rx.Observable.of
来包装单值同步数据Rx.Observable.fromPromise
来包装单值异步数据Rx.Observable.fromEvent
来包装多值异步数据
const fs = require('fs');
const Rx = require('rxjs');
const EventEmitter = require('events');
class MyEventEmitter extends EventEmitter {
async executeAsync(asyncFunc, args) {
this.emit('开始');
try {
console.time('执行耗时');
const data = await asyncFunc(args);
this.emit('data', data);
console.timeEnd('执行耗时');
this.emit('结束');
} catch (err) {
console.log('出错了!');
this.emit('error', err);
}
}
}
const readFileAsArray = function(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
const lines = data.toString().split('\r\n');
resolve(lines);
});
});
};
const myEventEmitter = new MyEventEmitter();
myEventEmitter.executeAsync(readFileAsArray, './numbers.txt');
let dataObservable = Rx.Observable.fromEvent(myEventEmitter, 'data');
let subscription = dataObservable.subscribe(
(data) => {
console.log(`分别抢到了${data}块红包`);
},
(err) => {
console.error(err);
},
(complete) => {
console.info('complete!');
},
);
rxjs 还有很多重要的概念,比如生产者 Observe 和消费者 Observable、推拉模型、各种方便的操作符和函数式编程等等
Object.observe() This feature is obsolete