学习背景
上一章我们学习了同步和异步,了解了他们的优缺点。nodejs单线程模型为防止线程被阻塞.提供了大量的异步函数和异步IO。这一章中我们学习一下异步函数的实现方式。
实现同步
正常同步的代码看起来比较的简单,都自上而下逐步执行的。执行完这行才会执行下一行,比如执行一个加减乘除的运算。
实现异步
但是异步执行就没有那么简单直观,需要在调用的时候注册执行完成的通知,可以是回调方式也可以是事件通知,也可以是发布订阅。
callback 方案
- 概念
这个是js中异步的经典用法,callback称为回调,指在调用异步方法的时候传一个回调方法,等异步任务执行完毕后,自动调用这个回调方法。
- 代码
function a(callback) {
console.log("A:执行完再其他函数");
callback();
}
function b(callback) {
console.log("B:执行完再其他函数");
callback()
}
function c(callback) {
console.log("C:执行完再其他函数");
callback()
}
function test() {
a(() => {
console.log('A:执行完了,进入回调')
b(() => {
console.log('B:执行完了,进入回调')
c(() => {
console.log('C:执行完了,进入回调')
})
})
})
}
test()
输出顺序为
A:执行完再其他函数
A:执行完了,进入回调
B:执行完再其他函数
B:执行完了,进入回调
C:执行完再其他函数
C:执行完了,进入回调
- 总结
虽然callback方案简单直观,但是在顺序执行多个异步任务的时候容易嵌套多层回调函数,形成“回调地狱”,代码可读性下降,维护成本变高。为了解决这个问题,nodejs引入了primise的概念。
事件EventEmitter方案
- 概念
事件在html前端用的比较多,尤其是在处理用户点击事件。它包含事件监听者和事件触发者,监听者监听一个事件标志,等待事件触发者来触发这个事件标志。同时也可以用在异步处理上,当异步任务处理完成后可以触发完成事件,其他地方监听这个完成事件来监听异步任务的完成状态。
- 代码
const EventEmitter = require('events').EventEmitter;
const myEvent = new EventEmitter();
myEvent.on('finish', function () {
console.log('finish 事件触发');
});
setTimeout(function () {
myEvent.emit('finish');
}, 1000);
- 总结
event事件常用在多个事件或者多个回调的场景下,但是我们自己定义异步方法的时候用事件来实现并不是特别好。
发布订阅方案
发布订阅模式和事件有着异曲同工之妙,它里面包含三个模块分别是发布者,订阅者和处理中心。有点像报社发布报纸,报社是发布者,订阅者是订阅该报纸的用户。这一章节我们先知道有这个方案,后面我会再写详细的说明教程。
约定Promise方案
- 概念
字面理解Promise是约定的意思,这个是ES6中原生提供的一个对象。它出现的目的是为了解决callback方案遗留的问题
- 代码
const promiseA = new Promise(function (resolve, reject) {
console.log("AAA:start");
setTimeout(() => {
resolve()
}, 100)
});
const promiseB = new Promise(function (resolve, reject) {
console.log("BBB:start");
setTimeout(() => {
resolve()
}, 50)
});
promiseA.then(() => {
console.log("AAA:finish")
});
promiseB.then(() => {
console.log("BBB:finish")
})
执行结果
AAA:start
BBB:start
BBB:finish
AAA:finish
- 总结
这么看promise 并没有比callback好用太多,甚至可以理解为把callback分装成then 方法。但是promise有官方支持的语法糖async、await,通过这两个语法糖可以实现用同步的方法去写异步。下一章我们把promse这个知识点单独拎出来讲,如何用同步的方式去实现异步。