使用Promise时将变量传递到外部作用域的最佳实践有哪些?
问题根源
你遇到的是同步遍历逻辑和异步IO请求的控制流不匹配问题,具体如下:
cheerio.each是纯同步遍历方法,不会等待内部的异步fetchRedirect请求完成就会执行完整个循环- 你在循环内直接往
parsedResultspush metadata的时候,重定向请求还没返回结果,自然没法把最终url插入对象 - 最后执行
console.log(parsedResults)、关闭浏览器的时机远早于所有异步请求返回的时机,就算你在then里赋值也会出现部分请求还没完成、资源就被释放的问题
最佳实现方案
建议把同步遍历改成异步批量处理,用Promise.all统一等待所有重定向请求完成后再组装结果,修改后的代码如下:
(async function main() { const browser = await puppeteer.launch({ headless: true, }); const page = await browser.newPage(); await page.goto('https://www.example.com/?q=async+urls&s=s'); const content = await page.content(); const $ = cheerio.load(content); const fetchRedirect = async (url) => { try { let response = await doRequest(url); return response; } catch (err) { return false; } }; function doRequest(url) { return new Promise(function (resolve, reject) { request(url, function (error, res, body) { if (!error) { resolve(getPathFromUrl(res.request.uri.href)); } else { reject(error); } }); }); } function getPathFromUrl(url) { return url.split(/[?#]/)[0]; } // 核心修改:把cheerio对象转成普通数组,用异步map处理每个元素 const parsedResults = await Promise.all( $('td[itemprop="subjectOf"]').first().get().map(async (element) => { const $el = $(element); const originUrl = $el.find('a.title').attr('href'); const title = $el.find('a.title').text(); // 等待重定向请求返回 const redirectUrl = await fetchRedirect(originUrl); // 直接组装完整metadata返回 return { title, redirectUrl // 重定向后的url直接插入对象 }; }) ); // 这里打印的就是带完整重定向url的结果 console.log(parsedResults); await page.close() await browser.close(); })();
额外说明
- 上述方案是并行发起所有重定向请求,如果你爬取的目标站点有并发限制,可以把map改成for循环串行
await每个请求,避免被封禁 - 你已经引入了puppeteer的情况下,也可以直接用
page.goto的能力获取重定向地址,不需要额外引入request库,减少依赖
内容的提问来源于stack exchange,提问作者coolsaint
相关产品推荐
相关产品推荐

