背景
- 来公司接手的项目是一个Node.js开发的后端,Node的特点就是异步IO,在发送网络请求获取数据或者查询数据库的时候并不会等待结果,而是将当前操作放入到一个事件循环队列中,当操作发生后,通过回调来进行后续操作
- 发现异步IO的编程思维和同步IO还是非常不同,本文主要介绍异步编程中遇到的问题以及如何解决
同步和异步
- 首先介绍简单的一种情况:获取数据库中的数据
- 同步使用Java代码演示,使用Thread.sleep(1000)来模拟查询耗时
public static void main(String[] args) throws InterruptedException{
int input = 1;
int res = subOne(input);
System.out.println("获取结果为:" + res);
}
// 模拟调用数据数据的操作
public static int subOne(int input) throws InterruptedException {
Thread.sleep(1000);
if (input <= 0) {
return 0;
}
return input-1;
}
获取结果为:0
- 如果直接将这段代码翻译为js代码,会发现程序并没有像我们想象中那样运行,程序在1秒后结束,说明事件已经发生了,但是程序没有处理这个事件
// 模拟数据库耗时操作
var subOne = function(input) {
setTimeout(() => {
if (input <= 0) {
return 0;
}
return input-1;
}, 1000);
}
var main = function() {
var input = 1;
// 异步IO,不会等待结果返回,直接执行后续语句
var res = subOne(input);
console.log("获取结果为:" + res);
}
main();
获取结果为:undefined
[Done] exited with code=0 in 1.062 seconds
- 我们需要用异步编程的思维去转换代码,使用回调函数去处理事件,这样我们就了解最简单的异步编程了
// 给异步操作添加回调函数
var subOne = function(input, callback) {
setTimeout(() => {
if (input <= 0) {
return callback(0);
}
return callback(input-1);
}, 1000);
}
var main = function() {
var input = 1;
// 通过回调函数获取异步IO结果
subOne(input, function(res) {
console.log("获取结果为:" + res);
});
}
main();
获取结果为:0
[Done] exited with code=0 in 1.058 seconds
批量异步IO
- 如果我不是拿着一个数据去数据库中查询数据,而是拿着一组数据去查询,最终将结果组装返回,如何实现
- 同步使用Java代码演示
public static void main(String[] args) throws InterruptedException {
// 模拟一组数组
int[] inputs = {1, 2, 3, 4};
// 保存结果
int[] res = new int[inputs.length];
for (int i = 0; i < inputs.length; i++) {
res[i] = subOne(inputs[i]);
}
// 输出结果
for (int s : res) {
System.out.println("获取结果为:" + s);
}
}
// 模拟调用数据数据的操作
public static int subOne(int input) throws InterruptedException {
Thread.sleep(1000);
if (input <= 0) {
return 0;
}
return input-1;
}
获取结果为:0
获取结果为:1
获取结果为:2
获取结果为:3
- 将此代码直接转换为js代码,是无法实现我们的需求的,我们可以看到程序在1秒后结束,再次说明js在程序开始就把所有IO请求发送出去了,不会等待请求结果
// 模拟数据库耗时操作
var subOne = function(input) {
setTimeout(() => {
if (input <= 0) {
return 0;
}
return input-1;
}, 1000);
}
var main = function() {
// 数组
var input = [1, 2, 3, 4];
// 结果集
var res = [];
for (var i in input) {
res.push(subOne(input[i]));
}
// 输出结果
for (var i in res) {
console.log("获取结果为:" + res[i]);
}
}
main();
获取结果为:undefined
获取结果为:undefined
获取结果为:undefined
获取结果为:undefined
[Done] exited with code=0 in 1.05 seconds
- 使用异步编程思维修改代码,需要添加一个递归函数来实现批量异步操作
// 模拟异步操作
var subOne = function(num, callback) {
setTimeout(() => {return callback(num > 0 ? num-1 : 0);}, 1000)
}
// 使用递归函数来实现批量异步IO
var async = function(arr, res, callback) {
// 结束条件是数组中没有数据了
if (arr.length == 0) {
return callback();
}
// 每次从数组中拿出左边第一个值
var num = arr.shift();
subOne(num, function(io_res) {
// 将异步IO结果保存到结果数组中
res.push(io_res);
// 继续下一个异步操作
async(arr, res, function() {
return callback();
});
});
}
// 输入
var arr = [1, 2, 3, 4];
// 保存结果
var res = [];
async(arr, res, function() {
// 输出结果
for (var i in res) {
console.log("获取结果为:" + res[i]);
}
});
获取结果为:0
获取结果为:1
获取结果为:2
获取结果为:3
[Done] exited with code=0 in 4.061 seconds
异步IO互相依赖
- 还有一种复杂情况是我们拿着一组数组去数据库中查询,根据从数据库中获取的结果判断接下来如何操作
- 同步使用Java代码演示
public static void main(String[] args) throws InterruptedException {
Queue<Integer> queue = new LinkedList<>();
queue.offer(2);
queue.offer(2);
queue.offer(2);
while (!queue.isEmpty()) {
// 调用一个IO函数
int res = subOne(queue.poll());
System.out.println("获取结果为:" + res);
// 将IO函数获取的结果加入到队列中继续执行
if (res != 0) {
queue.offer(res);
}
}
}
// 减1函数
public static int subOne(int num) throws InterruptedException {
Thread.sleep(1000);
return num > 0 ? num-1 : 0;
}
获取结果为:1
获取结果为:1
获取结果为:1
获取结果为:0
获取结果为:0
获取结果为:0
- 异步使用JS代码演示
// 模拟异步操作
var subOne = function(num, callback) {
setTimeout(() => {return callback(num > 0 ? num-1 : 0);}, 1000)
}
// 使用递归函数来实现批量异步IO
var async = function(arr, callback) {
// 结束条件是数组中没有数据了
if (arr.length == 0) {
return callback();
}
// 每次从数组中拿出左边第一个值
var num = arr.shift();
subOne(num, function(io_res) {
console.log("获取结果为:" + io_res);
// 判断结果是否加入到队列中继续执行IO
if (io_res != 0) {
arr.push(io_res);
}
// 继续下一个异步操作
async(arr, function() {
return callback();
});
});
}
// 输入
var arr = [2, 2, 2];
async(arr, function() {
console.log("函数结束!");
});
获取结果为:1
获取结果为:1
获取结果为:1
获取结果为:0
获取结果为:0
获取结果为:0
函数结束!
[Done] exited with code=0 in 6.076 seconds