You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用async.each时循环未等待异步任务完成的问题排查

问题:使用async.each遍历链接时,Nightmare.js请求未按顺序等待完成

你遇到的核心问题是async.each并没有感知到getPrecos函数里的异步操作,所以它会立刻触发所有迭代,而不会等待每个请求完成后再执行下一个。

问题根源

async.each要求你的迭代函数(也就是getPrecos)必须通过两种方式之一告知async任务已完成:

  1. 接受一个callback参数,在异步操作结束后调用它
  2. 返回一个Promise,让async等待Promise resolve/reject

但你当前的getPrecos函数既没有接受callback,也没有返回Promise——Nightmare的goto、evaluate这些操作都是异步的,但函数本身执行完就立刻结束了,async自然认为这个任务已经完成,直接启动下一个迭代。

解决方案:让迭代函数支持异步通知

我们可以给getPrecos添加callback参数,或者让它返回Promise,同时如果需要严格串行执行(一个请求完成再启动下一个),改用async.eachSeries会更贴合你的需求。以下是修正后的代码:

const Nightmare = require('nightmare');
const fs = require('fs');
const async = require('async');

const lerArquivo = () => {
  // 读取链接并过滤空行,避免无效请求
  const links = fs.readFileSync('links.txt', 'utf-8').split('\n').filter(link => link.trim() !== '');

  // 使用eachSeries实现串行迭代:一个任务完成再启动下一个
  async.eachSeries(links, getPrecos, function(erro){
    if(erro){
      console.log('Erro geral: ');
      console.log(erro);
    }else{
      console.log('Todos os dados foram recuperados com sucesso!');
    }
  });
}

// 修改getPrecos,添加callback参数告知async任务状态
function getPrecos (endereco, callback) {
  console.log('Recuperando dados de ' + endereco);
  const nightmare = new Nightmare({show: false, gotoTimeout: 999999999, waitTimeout: 999999999});

  // 合并Nightmare操作链,统一处理成功/失败
  nightmare.goto(endereco)
    .wait('input[id="edit-cep-part1"]')
    .type('input[id="edit-cep-part1"]', '51030')
    .wait('input[id="edit-cep-part2"]')
    .type('input[id="edit-cep-part2"]', '560')
    .click('input[value="Confirmar"]')
    .wait('#plans-tab')
    .evaluate(function(){
      return Array.from(document.querySelectorAll('tr.body')).map(element => element.innerText);
    })
    .end()
    .then(function(result){
      console.log(result);
      callback(null); // 告知async:当前任务完成,无错误
    })
    .catch(function(e){
      console.error('Erro ao recuperar dados de', endereco, e);
      callback(e); // 告知async:当前任务出错,终止迭代(或继续,取决于async配置)
    });
};

lerArquivo();

关键修改说明

  1. 添加callback参数:这是async库迭代函数的标准写法,通过callback(null)标记任务成功完成,callback(error)标记任务失败,让async明确知道何时进行下一次迭代。
  2. 改用async.eachSeries:如果你希望严格按顺序执行请求(一个完成再启动下一个),eachSeries是更合适的选择——原async.each默认是并行启动所有任务,这也是你看到所有启动日志先批量输出的原因。
  3. 合并Nightmare操作链:你之前拆分的try/catch块无法捕获Nightmare链式调用的异步错误,合并后可以通过.then和.catch统一处理成功和失败场景。
  4. 过滤空行:读取文件时split('\n')可能产生空字符串,用filter去掉这些无效链接,避免不必要的错误。

修改后,输出就会符合你的预期:

Recuperando dados de link1
// 输出link1的返回数据
Recuperando dados de link2
// 输出link2的返回数据
Recuperando dados de link3
// 输出link3的返回数据

内容的提问来源于stack exchange,提问作者Joao Victor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:19:25