zoukankan      html  css  js  c++  java
  • 1.node.js在遇到“循环+异步”时的注意事项

    原文:https://blog.csdn.net/fangjian1204/article/details/50585073

    自从使用Node.js以来,一直困扰着我的就是异步了。老是碰到一些,不按照代码书写顺序运行的情况,虽然网上的信息很多。但是找到能浅显易懂的很少。这里就找到一篇关于循环+异步的处理防范的案列。供大家参考。

    ps:贴出我写的伪代码示例,以便以后查看。

    /*
     * @Author: ChunHai.Hu
     * @Date: 2018-04-04 09:50:23
     * @Last Modified by: ChunHai.Hu
     * @Last Modified time: 2018-04-04 14:16:20
     */
    "use strict"
    var RESULT = [ { id: 1,
        name: '1金币',
        good_url: 'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=231399882,1844656341&fm=27&gp=0.jpg',
        cost: 10,
        is_time: 1,
        quantity: 495,
        type: 1,
        sequence: 1 },
      { id: 2,
        name: '2金币',
        good_url: 'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=231399882,1844656341&fm=27&gp=0.jpg',
        cost: 20,
        is_time: 0,
    quantity: 499,
        type: 1,
        sequence: 2 },
      { id: 3,
        name: '10金币',
        good_url: 'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=231399882,1844656341&fm=27&gp=0.jpg',
        cost: 100,
        is_time: 1,
        quantity: 496,
        type: 1,
        sequence: 3 } ];
    //声明迭代器进行is_done的数据验证。
    var Iteractor = function (n) {
        if (n >= RESULT.length) {
            console.log(RESULT);//最终迭代器的结果
            return;
        } else {
            isDone(RESULT[n].id, userId, now, function (res) {//此为数据库操作方法
                if (8 == res.state) {
                    RESULT[n].is_done = 1;//已经抢过,按钮灰色
              n++;
                    Iteractor(n);
                } else if (1 == res.state) {
                    // console.log("i num:" + i);
                    RESULT[n].is_done = 0;//未抢过,按钮彩色
                    n++;
                    Iteractor(n);
                } else {
                    Response.end({ status: 4, message: "server busy" });
                    return;
                }
            });
        }
    
    };
    Iteractor(0);

    nodejs的特征

    nodejs的最大特征就是一切都是基于事件的,从而导致一切都是异步的。nodejs的速度为什么快,其原理和nginx一样,他们都是通过事件回调来处理请求的,从而导致了整个处理过程中,不会阻塞nodejs,因此,其在同一时间内可以处理大量的请求,而这种优越性在你的请求是IO密集型的情况下,表现的尤为突出。下面的例子简单说明了基于异步事件的nodejs的处理流程:

    var send_data = function(req,res){
        sql = 'SELECT gid,name,image_url,price,create_time,describes,selluid FROM goods WHERE status=? LIMIT ?,?';
        connection.query(sql, [0,0,6], function(err, rows, fields) {
            if (err) throw err;
            console.log("输出:在这里处理数据库操作的结果");
        }); 
        console.log("输出:这是数据库操作后的语句");
    };
     

    使用过nodejs的程序员应该很容易知道,该函数的输出结果是:

    输出:这是数据库操作后的语句
    输出:在这里处理数据库操作的结果

    原因很简单,上面的查询语句并不是立即执行,而是放入待执行的队列中就立即返回,然后继续执行后面的语句;当数据库操作结束之后,会触发某个事件,告诉nodejs数据库操作已经完成,于是nodejs就执行原先设定的回调函数,对数据库的执行结果进行处理。这正是nodejs高效的地方,然而,凡事总有两面性,nodejs在高效的同时,也增加了程序员编写程序的复杂性,因为异步程序和以往的同步程序有很大的区别,下面我们来看一个常见的注意事项。

    for循环+异步操作

    一个很经典的问题就是在循环中遇到回调函数:

    var fs = require('fs');
    var files = ['a.txt','b.txt','c.txt'];
    
    for(var i=0; i < files.length; i++) {
        fs.readFile(files[i], 'utf-8', function(err, contents) {
            console.log(files[i] + ': ' + contents);
        });
    }

    假设这三个文件的内容分别为:AAA、BBB、CCC,我们期望的结果是:

    a.txt: AAA
    b.txt: BBB
    c.txt: CCC

    而实际的结果却是:

    undefined: AAA
    undefined: BBB
    undefined: CCC

    这是为什么呢?如果我们在循环内部把i的值打印出来,可以看出,三次输出的数据都是3,也就是files.length的值。也就是说,fs.readFile的回调函数中访问到的i值都是循环结束后的值,因此files[i]的值为undefined。解决此问题有很多方法,这里利用js函数编程的特性,建立一个闭包来保存每次需要的i值:

    var fs = require('fs');
    var files = ['a.txt','b.txt','c.txt'];
    
    for(var i=0; i < files.length; i++) {
        (function(i) {
            fs.readFile(files[i], 'utf-8', function(err, contents) {
                console.log(files[i] + ': ' + contents);
            });
        })(i);
    }

    由于运行时闭包的存在,该匿名函数中定义的变量(包括参数表)在它内部的函数(fs.readFile 的回调函数)执行完毕之前都不会释放,因此我们在其中访问到的 i 就分别是不同的闭包实例,这个实例是在循环体执行的过程中创建的,保留了不同的值。这里使用闭包是为了更清楚的看到上面输出undefined的原因,其实,还可以有更简单的方法:

    var fs = require('fs');
    var files = ['a.txt', 'b.txt', 'c.txt'];
    
    files.forEach(function(filename) {
        fs.readFile(filename, 'utf-8', function(err, contents) {
            console.log(filename + ': ' + contents);
        });
    });

    有关联的多条sql查询操作

    从上面的for循环可以清楚的看到异步编程与同步编程的不同:虽然高效,但是坑很多。再比如:如果我们有需要进行两次sql操作,但是有明确的需要,第二次必须要在第一次完成之后进行,怎么办?这很简单,只需要把第二次操作写在第一次的回调函数内部即可,因为第一次的回调函数触发的前提就是其已经执行完毕。但是如果第二次操作需要第一次操作返回的数据作为查询条件,而且要把两次结果合并起来返回,该如何处理呢?是如下这样吗?

    var send_data = function(req,res){
        sql = 'SELECT gid,name,image_url,price,create_time,describes,selluid FROM goods WHERE status=? LIMIT ?,?';
        connection.query(sql, [0,0,6], function(err, rows, fields) {
            if (err) throw err;
            rows.forEach(function(item){
                sql = "SELECT tag_name FROM tag,tag_goods WHERE tag_goods.gid=? AND tag_goods.tagid=tag.tagid";
                connection.query(sql, item.gid, function(err, tags, fields){
                    if (err) throw err;
                    item.tags = tags;
                });
            }); 
            res.render('index', {supplies:rows, login:req.session.login}); 
        }
    };

    上面的例子是先查询商品的信息,然后对每一个商品,用其id去查询标签列表,并添加到每条商品信息中。上面返回的结果真的会和期望的一样吗?然而,最后仅仅返回了不包含标签的商品信息,即还没等到内层查询执行结束,res.render()方法就已经返回了。虽然我们保证了第二条查询在第一条查询结束之后再执行,但我们无法保证返回语句在第二条查询结束之后再返回。具体的解决方法可能有多种,这里我们使用async模块来解决这里的同步问题。

    ASync函数介绍

    async主要实现了很多有用的函数,例如:

    • each: 如果想对同一个集合中的所有元素都执行同一个异步操作。
    • map: 对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与each的区别是,each只关心操作不管最后的值,而map关心的最后产生的值。
    • series: 串行执行,一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数。
    • parallel: 并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。
    • 其它

    很明显,这里我们可以使用map函数来实现我们的需求。该方法的原型为:map(arr, iterator(item, callback), callback(err, results));也就是说,我们用arr中的每一个元素item迭代调用iterator()方法,并把每次的结果保存下来,当迭代完之后,把结果汇聚起来给results调用callback()方法。应用此方法,我们的程序修改为:

    var async = require('async');
    
    var send_data = function(req,res){
        sql = 'SELECT gid,name,image_url,price,create_time,describes,selluid FROM goods WHERE status=? LIMIT ?,?';
        connection.query(sql, [0,0,6], function(err, rows, fields) {
            if (err) throw err;
            async.map(rows, function(item, callback) {
                sql = "SELECT tag_name FROM tag,tag_goods WHERE tag_goods.gid=? AND tag_goods.tagid=tag.tagid";
                connection.query(sql, item.gid, function(err, tags, fields){
                    item.tags = tags;
                    callback(null, item);
                });
            }, function(err,results) {
                res.render('index', {supplies:results, login:req.session.login});
            });
        }); 
    };

    此时,第二个sql语句每次查询到的tag被保存到item中,等所有的查询结束后,调用callback(null, item);即把所有的数据传递给results参数,最后统一发送给浏览器。此时发送的商品中,就包含了商品标签tag了。

  • 相关阅读:
    js分页--存储数据并进行分页
    纯CSS多级菜单
    ini文件操作
    XML文件数据操作
    身份证号码验证
    将DataSet(DataTable)转换成JSON格式(生成JS文件存储)
    将JSON(List集合)转成DataSet(DataTable)
    js打印(控件)及多种方式
    了解切面编程思想
    20190429版本-测试过程回溯
  • 原文地址:https://www.cnblogs.com/Nick-Hu/p/8715869.html
Copyright © 2011-2022 走看看