Node异步IO&异步编程

背景

  1. 来公司接手的项目是一个Node.js开发的后端,Node的特点就是异步IO,在发送网络请求获取数据或者查询数据库的时候并不会等待结果,而是将当前操作放入到一个事件循环队列中,当操作发生后,通过回调来进行后续操作
  2. 发现异步IO的编程思维和同步IO还是非常不同,本文主要介绍异步编程中遇到的问题以及如何解决

同步和异步

  1. 首先介绍简单的一种情况:获取数据库中的数据
  2. 同步使用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
  1. 如果直接将这段代码翻译为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
  1. 我们需要用异步编程的思维去转换代码,使用回调函数去处理事件,这样我们就了解最简单的异步编程了
    // 给异步操作添加回调函数
    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

  1. 如果我不是拿着一个数据去数据库中查询数据,而是拿着一组数据去查询,最终将结果组装返回,如何实现
  2. 同步使用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
  1. 将此代码直接转换为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
  1. 使用异步编程思维修改代码,需要添加一个递归函数来实现批量异步操作
    // 模拟异步操作
    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互相依赖

  1. 还有一种复杂情况是我们拿着一组数组去数据库中查询,根据从数据库中获取的结果判断接下来如何操作
  2. 同步使用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
  1. 异步使用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
posted @ 2022-03-11 15:14  BD哥  阅读(102)  评论(0)    收藏  举报