请教关于Nodejs异步循环的问题

请教关于Nodejs异步循环的问题

代码很简单,如下:

var find;
data.some(function(ip) {
    client.sismember(url+':ip', ip, function(err, mem) {
        !mem && (find = ip);
        console.log('redis:' + find);
    });

    console.log(find);
    return find;
});

原理很简单:

  • 我先some循环一个IP数组
  • 如果数组中的IP不存在redis中,我就将find赋值为当前IP
  • 如果循环过程中找到了IP,我就跳出循环,不再一直查询redis

结果,代码中有两个console:

  • 先循环数组数据,find一直是undefined,因此不会跳出循环
  • 之后执行所有redis查询结果,并重复给find复制,并打印出来

问题:

  • 首先结果不是我想要的
  • 效率差,我循环100条就查询100次redis

请问如何解决?


9 回复

要解决这个问题,我们需要处理Node.js中的异步操作。Array.prototype.some方法本身是同步的,但其中的回调函数是异步的。因此,我们需要一种方法来确保所有的异步操作完成后再进行下一步。

我们可以使用async/await语法结合Promise来简化异步逻辑。这里是一个改进后的示例代码:

const async = require('async');

var data = ['ip1', 'ip2', 'ip3']; // 示例数据
var url = 'http://example.com'; // 示例URL
var client = {}; // 假设client对象已经定义好

async function checkIPs() {
    let find;

    for (let ip of data) {
        await new Promise((resolve, reject) => {
            client.sismember(`${url}:ip`, ip, (err, mem) => {
                if (err) {
                    reject(err);
                } else {
                    if (!mem) {
                        find = ip;
                        resolve();
                    }
                }
            });
        });

        if (find) {
            break;
        }
    }

    console.log('find:', find);
}

checkIPs().catch(console.error);

解释

  1. 引入async模块

    • 我们使用async库来帮助管理异步操作。虽然在这个例子中我们也可以不使用async库,但使用它可以简化代码并提高可读性。
  2. 定义checkIPs函数

    • 这个函数负责遍历data数组,并检查每个IP是否存在于Redis中。
    • 使用for...of循环代替Array.prototype.some,因为some方法不适合处理异步操作。
  3. 使用awaitPromise

    • await new Promise(...)用于等待异步操作完成。当client.sismember返回的结果满足条件时,resolve()被调用,从而继续执行后续代码。
    • 如果找到匹配的IP,则设置find变量并跳出循环。
  4. 错误处理

    • await表达式中捕获任何可能发生的错误,并通过.catch处理它们。
  5. 调用checkIPs函数

    • 最后,我们调用checkIPs函数并处理可能出现的错误。

这种方法确保了所有的异步操作按顺序执行,并且可以在找到第一个匹配的IP时立即停止。这样可以提高效率并确保程序的行为符合预期。


不知所云

楼主是否需要如下两个函数?:

函数1):检查指定的IP数组是否存在于redis中,返回所有不存在于redis中的IP数组: 函数2):将指定的IP数组存放于redis中

async.js async.parallelLimit(…) 不知是否能解决你的问题

async.mapSeries将执行转成同步,可以试试

两个办法 要么转同步 . 要么回调函数里加上状态判断 . 建议第二种方式 .

var find;
var howdo = require('howdo');

howdo.each(data, function(index, ip, done){
       if(find){
		     return done(null);
		}
		
     client.sismember(url+':ip', ip, function(err, mem) {
	    if(err){
		    return done(err);
		}
		
		if(!mem){
		    find = ip;
			console.log('redis:' + find);
		}
		
		done(null);
    });
}).together(function(err){
     if(err){
	     return console.log(err);
	 }
	 
	 console.log(find);
});

你的代码中存在的主要问题是 client.sismember 是一个异步操作,而你在 data.some 中使用了同步的循环逻辑。这导致了你看到的结果并不是你预期的那样。你希望在找到匹配项后能够提前退出循环,而不是每次循环都完成后再处理结果。

解决方案

你可以使用递归或异步迭代器来实现这一需求。这里我们用递归的方法来解决这个问题:

function checkIPs(ips, index = 0, client, url, callback) {
    if (index >= ips.length) {
        return callback(null, null); // 所有IP都已经检查过,没有找到
    }

    const ip = ips[index];
    client.sismember(url + ':ip', ip, function(err, mem) {
        if (err) return callback(err);

        if (!mem) { // 如果IP不存在于集合中
            return callback(null, ip); // 返回当前IP并停止进一步的检查
        }

        // 否则继续检查下一个IP
        checkIPs(ips, index + 1, client, url, callback);
    });
}

// 使用方法:
checkIPs(data, 0, client, url, function(err, find) {
    if (err) throw err;
    console.log('发现的IP:', find);
});

解释

  • 递归函数checkIPs 函数递归地遍历IP数组,直到找到第一个不在集合中的IP或者遍历完所有IP。
  • 回调机制:当找到符合条件的IP时,立即调用回调函数并返回该IP,从而避免不必要的后续检查。
  • 错误处理:在每个回调函数内部处理可能发生的错误。

这种方法确保了在找到第一个符合条件的IP时能够立即停止检查,并且提高了整体效率,因为你不需要等待所有的异步操作完成。

回到顶部