异步循环嵌套异步循环是否合理?JavaScript实现优化咨询
优化异步嵌套循环的JavaScript(NightmareJS + SQL Server)实现
我有一段JavaScript代码,功能是读取.txt文件生成对象数组,遍历数组元素,再用NightmareJS请求每个元素里的链接,最后把结果写入SQL Server。代码能正常运行,但这种多层异步嵌套的写法感觉只是临时方案,而且控制台先输出完所有日志才开始发起请求,想问问有没有更规范、更优的实现方式?
现有代码
const Nightmare = require('nightmare'); const fs = require('fs'); const async = require('async'); const sql = require('mssql'); var links = recuperarLinks(); function recuperarLinks(){ //Read the txt file and return an array } const bigFunction = () => { var aparelho = ''; async.eachSeries(links, async function (link) { console.log('Zip Code: ' + link.zipCode); async.eachSeries(link.links, async function(url){ console.log('URL: ' + url); try { await nightmare.goto(link2) .evaluate(function () { //return some elements }) .end() .then(function (result) { //ajust the result dadosAjustados.forEach(function (obj) { //save the data saveDatabase(obj, link.cep); }); }); } catch (e) { console.error(e); } }, function(err){ console.log('Erro: '); console.log(err); }) }, function (erro) { if (erro) { console.log('Erro: '); console.log(erro); } }); } async function salvarBanco(dados, cep){ const pool = new sql.ConnectionPool({ user: 'sa', password: 'xxx', server: 'xxx', database: 'xxx' }); pool.connect().then(function(){ const request = new sql.Request(pool); const insert = "some insert" request.query(insert).then(function(recordset){ console.log('Dado inserido'); pool.close(); }).catch(function(err){ console.log(err); pool.close(); }) }).catch(function(err){ console.log(err); }); } lerArquivo();
当前控制台输出
Fetching Data from cep 1 Fetching Data from url 1 Fetching Data from cep 2 Fetching Data from url 2 Fetching Data from cep 3 Fetching Data from url 3
输出完成后才开始发起请求。
好问题!这种多层async.eachSeries嵌套确实容易让代码变得臃肿难维护,而且你提到的日志先输出再执行请求的问题,本质是async库和async/await混用导致的时序偏差。咱们可以用原生的async/await结合for...of循环来重构,代码会更清晰易读,还能解决时序问题。
优化后的实现方案
我从几个核心点入手重构了代码,兼顾可读性、性能和时序正确性:
const Nightmare = require('nightmare'); const fs = require('fs'); const sql = require('mssql'); // 全局复用数据库连接池(避免频繁创建销毁连接的开销) let sqlPool; // 初始化数据库连接池 async function initSqlPool() { sqlPool = new sql.ConnectionPool({ user: 'sa', password: 'xxx', server: 'xxx', database: 'xxx' }); await sqlPool.connect(); console.log('数据库连接池已初始化'); } function recuperarLinks(){ //Read the txt file and return an array } // 处理单个邮编组下的所有链接 async function processLinkGroup(linkGroup) { console.log('Zip Code: ' + linkGroup.zipCode); // 串行处理当前邮编下的每个链接 for (const url of linkGroup.links) { console.log('URL: ' + url); try { // 每个请求新建Nightmare实例,避免上下文污染 const nightmare = Nightmare(); // 统一用async/await处理Nightmare异步操作,时序更可控 const result = await nightmare .goto(url) // 修正原代码的link2笔误,应该用当前遍历的url .evaluate(() => { // 返回需要提取的页面元素,示例写法 return document.querySelector('.target-element')?.innerText; }) .end(); // 处理并格式化结果(替换成你实际的逻辑) const dadosAjustados = formatResult(result); // 串行写入数据库 for (const obj of dadosAjustados) { await salvarBanco(obj, linkGroup.zipCode); } } catch (e) { console.error(`处理URL ${url} 时出错:`, e); } } } // 数据库写入函数(复用全局连接池) async function salvarBanco(dados, cep) { try { const request = new sql.Request(sqlPool); // 用参数化查询防止SQL注入,替换成你的实际表和字段 const insert = `INSERT INTO YourTable (field1, field2, zip_code) VALUES (@f1, @f2, @zip)`; request.input('f1', sql.VarChar, dados.field1); request.input('f2', sql.Int, dados.field2); request.input('zip', sql.VarChar, cep); await request.query(insert); console.log('Dado inserido'); } catch (err) { console.error('写入数据库出错:', err); } } // 主流程入口 async function main() { try { await initSqlPool(); const links = recuperarLinks(); // 串行处理每个邮编组 for (const linkGroup of links) { await processLinkGroup(linkGroup); } console.log('所有任务处理完成'); await sqlPool.close(); } catch (err) { console.error('主流程出错:', err); if (sqlPool) await sqlPool.close(); } } // 启动程序 main();
核心优化点说明
- 时序问题解决:用
for...of配合await,每一步都会等待前一个操作完成才执行下一个,日志会和实际请求流程同步,不会出现先全输出日志再执行的情况。 - 代码可读性提升:把大函数拆分成
initSqlPool、processLinkGroup等单一职责的小函数,逻辑分层清晰,更容易调试和维护。 - 性能优化:全局复用数据库连接池,避免频繁创建销毁连接的开销;每个请求新建Nightmare实例,防止不同请求之间的上下文污染。
- 安全性提升:使用参数化查询代替字符串拼接,彻底避免SQL注入风险。
内容的提问来源于stack exchange,提问作者Joao Victor
相关产品推荐
相关产品推荐

