首先先明白一点,异步编程的目标就是怎样让它更像同步编程。
异步编程的解决方案有哪些?
1、回调
2、事件发布订阅
3、Promise
4、Generator
5、async/await
回调
由于node涉及大量的异步操作,所以回调非常常见。
fs.readFile('某个文件', function (err, data) {
if (err) throw err;
console.log(data);
});
回调的问题,第一个就是异常处理,无法再使用try-catch捕获错误,Node在处理异常有一个约定,将异常作为回调的第一个实参传回,如果为空表示没有出错。
异步多级依赖的情况下嵌套非常深,代码难以阅读的维护,也就是我们常说的回调地狱的问题。
发布订阅
订阅事件实现了一个事件与多个回调函数的关联(一对多),是多个异步之间的协作方案。
回调函数可以任意的添加和删除,也可以隔离业务逻辑,保持业务逻辑单元的职责单一。
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
使用事件的方式时,执行流程需要被预先设定,这是有发布订阅模式机制所决定的,Promise 可以先执行异步调用,延迟传递处理的方法。
Promise
Promise是高级接口,发布订阅模式是低级接口。低级接口可以构成更多更复杂的场景,高级接口一旦定义,不太容易变化,不再有低级接口的灵活性,但对于解决典型问题非常有效。
fetch(url, options).then(function(response) {
console.log(res);
}, function(error) {
console.log(e.code) // ENOTFOUND
})
生成器Generators
co 是 TJ 大神基于 ES6 generator 的异步解决方案。要理解 co 你得先理解 ES6 generator,这里就不赘述了。co 最大的好处就是能让你把异步的代码流程用同步的方式写出来,并且可以用 try/catch:
co(function *(){
try {
var res = yield fetch(url, options);
console.log(res);
} catch(e) {
console.log(e.code) // ENOTFOUND
}
})()
但用 co 的一个代价是 yield 后面的函数必须返回一个 Thunk 或者一个 Promise,对于现有的 API 也得进行一定程度的二次封装。
async/await
async function showStuff () {
var data = await loadData() // loadData 返回一个 Promise
console.log(data) // data 已经加载完毕
}
async function todo () {
await showStuff() // async 函数默认返回一个 Promise, 所以可以 await 另一个 async 函数
// 这里 showStuff 已经执行完毕
}
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
可以看到,和用 co 写出来的代码很像,但语意上更清晰。因为本质上 ES7 async/await 就是基于 Promise + generator 的一套语法糖。
总结:解决问题的方式有很多种,我们还是需要具体问题具体分析,根据不同的场景使用最佳的编码方式才是最好的选择。