Javascript Node.js:在整个应用程序中共享连接对象
我在使用木偶演员实现Javascript Node.js:在整个应用程序中共享连接对象,javascript,node.js,Javascript,Node.js,我在使用木偶演员实现通用池时遇到问题。下面是我的代码的相关部分 更新 感谢@Jacob的帮助,我对这个概念和它的工作原理更加清楚,代码也更加可读和清晰。我仍然有一些问题,每次请求都会创建一个通用池。如何确保每次都使用相同的通用池,而不是创建新的通用池 浏览器池.js const genericPool = require('generic-pool'); const puppeteer = require('puppeteer'); class BrowserPool { static a
通用池
时遇到问题。下面是我的代码的相关部分
更新
感谢@Jacob的帮助,我对这个概念和它的工作原理更加清楚,代码也更加可读和清晰。我仍然有一些问题,每次请求都会创建一个通用池。如何确保每次都使用相同的通用池,而不是创建新的通用池
浏览器池.js
const genericPool = require('generic-pool');
const puppeteer = require('puppeteer');
class BrowserPool {
static async getPool() {
const browserParams = process.env.NODE_ENV == 'development' ? {
headless: false,
devtools: false,
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
}
:
{
headless: true,
devtools: false,
executablePath: 'google-chrome-unstable',
args: ['--no-sandbox', '--disable-dev-shm-usage']
};
const factory = {
create: function() {
return puppeteer.launch(browserParams);
},
destroy: function(instance) {
console.log('closing browser in hrere.....');
instance.close();
}
};
const opts = {
max: 5
};
this.myBrowserPool = genericPool.createPool(factory, opts);
}
static async returnPool() {
if (this.myBrowserPool == "") {
getPool();
}
return this.myBrowserPool.acquire();
}
}
BrowserPool.myBrowserPool = null;
module.exports = BrowserPool;
const BrowserPool = require('./browser-pool');
async function performExport(params){
const myPool = BrowserPool.getPool();
const resp = BrowserPool.myBrowserPool.acquire().then(async function(client){
try {
const url = config.get('url');
const page = await client.newPage();
await page.goto(url, {waitUntil: ['networkidle2', 'domcontentloaded']});
let gotoUrl = `${url}/dashboards/${exportParams.dashboardId}?csv_export_id=${exportParams.csvExportId}`;
//more processing
await page.goto(gotoUrl, {waitUntil: 'networkidle2' })
await myPool().myBrowserPool.release(client);
return Data;
} catch(err) {
try {
const l = await BrowserPool.myBrowserPool.destroy(client);
} catch(e) {
}
return err;
}
}).catch(function(err) {
return err;
});
return resp;
}
module.exports.performExport = performExport;
处理导出.js
const genericPool = require('generic-pool');
const puppeteer = require('puppeteer');
class BrowserPool {
static async getPool() {
const browserParams = process.env.NODE_ENV == 'development' ? {
headless: false,
devtools: false,
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
}
:
{
headless: true,
devtools: false,
executablePath: 'google-chrome-unstable',
args: ['--no-sandbox', '--disable-dev-shm-usage']
};
const factory = {
create: function() {
return puppeteer.launch(browserParams);
},
destroy: function(instance) {
console.log('closing browser in hrere.....');
instance.close();
}
};
const opts = {
max: 5
};
this.myBrowserPool = genericPool.createPool(factory, opts);
}
static async returnPool() {
if (this.myBrowserPool == "") {
getPool();
}
return this.myBrowserPool.acquire();
}
}
BrowserPool.myBrowserPool = null;
module.exports = BrowserPool;
const BrowserPool = require('./browser-pool');
async function performExport(params){
const myPool = BrowserPool.getPool();
const resp = BrowserPool.myBrowserPool.acquire().then(async function(client){
try {
const url = config.get('url');
const page = await client.newPage();
await page.goto(url, {waitUntil: ['networkidle2', 'domcontentloaded']});
let gotoUrl = `${url}/dashboards/${exportParams.dashboardId}?csv_export_id=${exportParams.csvExportId}`;
//more processing
await page.goto(gotoUrl, {waitUntil: 'networkidle2' })
await myPool().myBrowserPool.release(client);
return Data;
} catch(err) {
try {
const l = await BrowserPool.myBrowserPool.destroy(client);
} catch(e) {
}
return err;
}
}).catch(function(err) {
return err;
});
return resp;
}
module.exports.performExport = performExport;
我的理解是
1) 当应用程序启动时,我可以启动例如2个chromium
实例,然后每当我想访问一个页面时,我都可以使用这两个连接中的任何一个,因此浏览器基本上是开放的,我们可以提高性能,因为浏览器启动可能需要时间。这是正确的吗
2) 我应该将acquire()
代码放在哪里,我知道这应该放在app.js
中,所以我们在应用程序启动时获取实例,但是我的pupeter代码位于不同的文件中,我如何在包含我的pupeter代码的文件中传递浏览器引用
当我使用上述代码时,每次都会出现一个新的浏览器实例,并且不会考虑max
属性,它会根据请求的实例数打开
如果这是一件非常艰难的事情,我很抱歉,我可能还没有完全理解这个概念。任何澄清这一点的帮助都会非常有用。使用池时,您需要使用
.acquire()
获取对象,然后使用.release()
将对象返回池并提供给其他对象。如果不使用.release()
,您可能根本就没有池。我喜欢将此帮助器模式用于池:
class BrowserPool {
// ...
static async withBrowser(fn) {
const pool = BrowserPool.myBrowserPool;
const browser = await pool.acquire();
try {
await fn(browser);
} finally {
pool.release(browser);
}
}
}
在代码中的任何地方都可以这样使用:
await BrowserPool.withBrowser(async browser => {
await browser.doSomeThing();
await browser.doSomeThingElse();
});
关键是finally
子句确保无论任务完成还是抛出错误,每次都会将浏览器释放回池中
听起来您可能也有了max
选项的概念,并且希望浏览器实例能够生成到max
。相反,max
的意思是“只创建最多max
个数的资源”。例如,如果您试图获取第六个资源而没有释放任何内容,则acquire(…)
调用将阻塞,直到有一个项目返回到池中
另一方面,min
选项意味着“随时至少保留这么多的物品”,您可以使用它来预先分配资源。如果希望提前创建5个项目,请将min
设置为5。如果希望创建5个项目,并且只创建5个项目,请将min
和max
都设置为5
更新:
我注意到,在您的原始代码中,如果出现错误,您将进行销毁,如果没有错误,则将其释放。仍然希望像我这样的包装器函数能够集中所有资源获取/释放逻辑(SRP方法)。以下是如何更新它以在出现错误时自动销毁:
class BrowserPool {
// ...
static async withBrowser(fn) {
const pool = BrowserPool.myBrowserPool;
const browser = await pool.acquire();
try {
await fn(browser);
pool.release(browser);
} catch (err) {
await pool.destroy(browser);
throw err;
}
}
}
附录
如果采用异步函数,而不是混合使用异步函数和承诺回调,那么了解代码中发生了什么将更容易。以下是如何重写它:
async function performExport(params){
const myPool = BrowserPool.myBrowserPool;
const client = await myPool.acquire();
try {
const url = config.get('url');
const page = await client.newPage();
await page.goto(url, {waitUntil: ['networkidle2', 'domcontentloaded']});
let gotoUrl = `${url}/dashboards/${exportParams.dashboardId}?csv_export_id=${exportParams.csvExportId}`;
//more processing
await page.goto(gotoUrl, {waitUntil: 'networkidle2' })
await myPool.release(client);
return Data;
} catch(err) {
try {
const l = await myPool.destroy(client);
} catch(e) {
}
return err; // Are you sure you want to do this? Would suggest throw err.
}
}
非常感谢你的回答,我一直努力想完全理解这一点,这个回答很有帮助。我还试图理解,当我的应用程序启动时,我应该看到chrom实例已经启动了吗?或者只有在调用acquire时实例才会启动?在这种情况下,这与不在池中使用有何不同?您的意思是不调用发布;您是否在finally子句中调用release,比如在我的代码中,在异步函数中?它应该总是在承诺解决/拒绝后被调用。
destroy
是另一回事release
将项目返回到池中,以便以后的代码可以使用相同的对象destroy
适用于您永远不想再次使用该对象的情况。如果您在多个位置有池获取/释放代码,我建议您进行更改,因为这样很难在脑海中清晰地描述资源是如何维护的。我展示的模式的好处是,您将所有浏览器资源管理集中在一个地方。我只在这里留下评论。游泳池运作良好。但是,该代码没有同时运行任何请求,而且它还在它们自己的子进程中运行请求,因此它们无法共享浏览器池。解决方案是不使用池,而是使用工作队列库的并发控制。