使用async.each时循环未等待异步任务完成的问题排查
问题:使用async.each遍历链接时,Nightmare.js请求未按顺序等待完成
你遇到的核心问题是async.each并没有感知到getPrecos函数里的异步操作,所以它会立刻触发所有迭代,而不会等待每个请求完成后再执行下一个。
问题根源
async.each要求你的迭代函数(也就是getPrecos)必须通过两种方式之一告知async任务已完成:
- 接受一个
callback参数,在异步操作结束后调用它 - 返回一个
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();
关键修改说明
- 添加
callback参数:这是async库迭代函数的标准写法,通过callback(null)标记任务成功完成,callback(error)标记任务失败,让async明确知道何时进行下一次迭代。 - 改用
async.eachSeries:如果你希望严格按顺序执行请求(一个完成再启动下一个),eachSeries是更合适的选择——原async.each默认是并行启动所有任务,这也是你看到所有启动日志先批量输出的原因。 - 合并Nightmare操作链:你之前拆分的try/catch块无法捕获Nightmare链式调用的异步错误,合并后可以通过
.then和.catch统一处理成功和失败场景。 - 过滤空行:读取文件时
split('\n')可能产生空字符串,用filter去掉这些无效链接,避免不必要的错误。
修改后,输出就会符合你的预期:
Recuperando dados de link1 // 输出link1的返回数据 Recuperando dados de link2 // 输出link2的返回数据 Recuperando dados de link3 // 输出link3的返回数据
内容的提问来源于stack exchange,提问作者Joao Victor
相关产品推荐
相关产品推荐

