1.node.js在遇到“循环+异步”时的注意事项

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

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

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了。

原文地址:https://www.cnblogs.com/Nick-Hu/p/8715869.html

时间: 2024-10-12 21:10:27

1.node.js在遇到“循环+异步”时的注意事项的相关文章

理解 node.js 的事件循环

node.js 的第一个基本观点是,I/O 操作是昂贵的: 目前的编程技术最大的浪费来自等待 I/O 操作的完成.有几种方法可以解决这些对性能的影响(来自Sam Rushing): 同步:依次处理单个请求. 优点:简单. 缺点:任何一个请求都会阻塞其余请求. 创建新进程:为每个请求创建一个进程处理 优点:容易. 缺点:扩展性不好,数百个连接意味着数百个进程.fork()是 Unix 程序员的锤子.因为它很有用,所有的问题都像是钉子.但这通常是多余的. 线程:为每个请求创建一个线程处理. 优点:容

Node.js~ioredis处理耗时请求时连接数瀑增

回到目录 关于redis连接数过高的解释 对于node.js开发环境里,使用传统的redis或者使用ioredis都是不错的选择,而在处理大数据请求程中,偶尔出现了连接池( redis服务端的最大可用连接数,默认为1万)不够用的情况,一般的提示如下: It was not possible to connect to the redis server(s); to create a disconnected multiplexer, disable AbortOnConnectFail 在red

利用async和await异步操作解决node.js里面fs模块异步读写,同步结果的问题

async await 解决异步问题,这两个关键字是es7提出的,所以测试,node和浏览器版本提高一些 async await 操作基于promise实现的 async await这两个关键字是一起使用,分开使用会报错 await 后面只能跟promise对象 不熟悉的promise异步操作的朋友,去看看我promise那边文章 Promise 解决多层嵌套,回调地狱什么叫回调地狱写一个实例,就是恶心的多层欠嵌套 function a(){ function b(){ function c()

node.js 异步式I/O 与事件驱动

Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式.这种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元. 阻塞与线程什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或网络通信(统称为 I/O 操作),通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为 阻塞.当 I/O 操作完毕时,操作系

Node.js的异步I/O

Linux操作系统的I/O模型 JAVA的NIO引入了异步I/O,而Node.js宣称的就是异步编程,I/O自然是异步的.其实操作系统在很早就引入了异步I/O的概念,如下图(摘自Unix网络编程中的图片): 我对上图的理解有几点: 从IO设备读取数据到用户内存的整个过程都是由系统内核来完成: 数据总是先被拷贝到内核缓冲区,再由内核缓冲区拷贝到用户内存: 除了异步I/O,其余4种I/O模型其实都是阻塞的,至少在数据从内核拷贝到用户内存时是阻塞的: 虽然异步I/O看上去是理想解决方案,但实现上现在用

Node.js 异步模式浅析

注:此文是node.js实战读后的总结. 在平常的脚本语言中都是同步进行的,比如php,服务器处理多个请求的方法就是并行这些脚本.多任务处理,多线程等等.但是这种处理方式也有一个问题:每一个进程或者线程都会耗费大量的系统资源.如果有一种方法可以最大化的利用CPU的计算能力和可用内存以减少资源浪费那就极好了.这样,我们的node.js就应运而生了. 上一个node.js最简单的异步编程案例: 1 var fs = require('fs'); 2 3 var file; 4 5 fs.open(

Node.js的线程和进程

http://www.admin10000.com/document/4196.html 前言 很多Node.js初学者都会有这样的疑惑,Node.js到底是单线程的还是多线程的?通过本章的学习,能够让读者较为清晰的理解Node.js对于单/多线程的关系和支持情况.同时本章还将列举一些让Node.js的web服务器线程阻塞的例子,最后会提供Node.js碰到这类cpu密集型问题的解决方案. 在学习本章之前,读者需要对Node.js有一个初步的认识,熟悉Node.js基本语法.cluster模块.

Node.js【4】简介、安装和配置、快速入门

笔记来自<Node.js开发指南>BYVoid编著 第1章 Node.js简介 Node.js是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为脚本语言世界的一等公民,在服务端堪与PHP.Python.Perl.Ruby平起平坐. Node.js可以作为服务器向用户提供服务,与PHP.Python.RubyonRails相比,它跳过了Apache.Nginx等HTTP服务器,直接面向前端开发. Node.js还可以调用C/C++的代码,这样可以充分利用已有的诸多函

深入浅出Node.js(一):什么是Node.js

Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟Joyent获得企业资助,再到今年发布Windows移植版本,Node.js的前景获得了技术社区的肯定.InfoQ一直在关注Node.js的发展,在今年的两次Qcon大会(北京站和杭州站)都有专门的讲座.为了更好地促进Node.js在国内的技术推广,我们决定开设“深入浅出Node.js”专栏,邀请来自Node.js领域的布道师.