路径:addons\web\static\src\env.js
这个文件的作用就是初始化env,主要是加载所有的服务。如orm, title, dialog等。
1、env.js 的加载时机前文我们讲过前端的启动函数,start.js,其中有这么两句,这里有两个函数makeEnv和startServices,都在同级目录的env.js里
const env = makeEnv(); await startServices(env); 2、 makeEnv() export function makeEnv() { return { bus: new EventBus(), services: {}, debug: odoo.debug, get isSmall() { throw new Error("UI service not initialized!"); }, }; }从代码可以看出,env有4个属性:
bus: 全局数据总线services:全局服务对象debug: 是否debug模式isSmall:判断手机端还是移动端这里抛出一个异常是为了在后面会覆盖这个方法。
从中可以看出,env最重要的其实就两个对象,一个是全局数据总线,负责组件之间通信,另一个就是service。
3、startServices启动所有在注册表中注册的服务,并且保证所有的依赖都得到满足。
首先获取所有在注册表中注册的服务serviceRegistry
初始化元数据SERVICES_METADATA, 主要是保存service中sync的内容,有什么作用还不清楚
主角开始上场: startServices
启动所有在注册表中注册的服务,并且保证每个服务的依赖都得到满足。
await Promise.resolve(); // 等待之前的异步操作都完成 const toStart = new Set(); // 初始化要启动的服务,因为每个服务都不同,所以用了set serviceRegistry.addEventListener // 这段没看懂,这里添加了一个事件监听,动态增加注册服务的 await _startServices(env, toStart); // 将env和toStart对象传入,干正事了 4._startServices(env, toStart) 4.1 填充 toStart 集合 const services = env.services; //获取了env中的service对象,这时候还是空的,啥也没有 // 根据注册表中的服务信息,初始化了toStart集合,里面都是创建好的实例对象,并且有一个name属性。 for (const [name, service] of serviceRegistry.getEntries()) { if (!(name in services)) { const namedService = Object.assign(Object.create(service), { name }); toStart.add(namedService); } } 4.2、findNext()关键的地方来了, 这里有一个关键的函数findNext(),先看看它的定义:
// 从toStart集合中遍历,有过集合中的服务有依赖并且依赖都已经满足了,则返回这个service,如果没有依赖,那么直接返回,找到第一个就返回,不叨叨,直到找不到,返回null function { for (const s of toStart) { if (s.dependencies) { if (s.dependencies.every((d) => d in services)) { return s; } } else { return s; } } return null; } 4.3 、启动服务,并填充到env的services中在start函数中,findNext函数返回的服务,第一步要最的就是从toStart 删除,这样遍历的次数会越来越少,我看过,odoo17一共有69个服务,while一共还要遍历69次,每次加载一个服务。 通过这种方式,很好的处理了服务之间的依赖关系,并且最大限度的实现了并行。
// start as many services in parallel as possible 并行启动尽可能多的服务 async function start() { let service = null; const proms = []; while ((service = findNext())) { const name = service.name; toStart.delete(service); // 删除要加载的服务 const entries = (service.dependencies || []).map((dep) => [dep, services[dep]]); const dependencies = Object.fromEntries(entries); let value; try { value = service.start(env, dependencies); // 调用start函数,并将返回值付给value } catch (e) { value = e; console.error(e); } if ("async"in service) { SERVICES_METADATA[name] = service.async; // 保存服务的元数据,后面可能会有用 } if (value instanceof Promise) { // 如果value是一个Promise proms.push( new Promise((resolve) => { value .then((val) => { services[name] = val || null; // 将promise的返回值保存到services中 }) .catch((error) => { services[name] = error; console.error("Can't load service '"+ name +"' because:", error); }) .finally(resolve); }) ); } else { services[service.name] = value || null; // 如果不是promise,直接将value保存到services中 } } await Promise.all(proms); // 等待所有的proms完成 if (proms.length) { return start(); } }到这里,前端js就完成了所有service的加载,要注意的是, env.services中保存的不是 services对象本身,而是service对象的start函数返回的对象。
这点很重要,每个service都要有start函数,而且要有返回值。
4.4、后面还有一段是异常处理 if (toStart.size) { const names = [...toStart].map((s) => s.name); const missingDeps = new Set(); [...toStart].forEach((s) => s.dependencies.forEach((dep) => { if (!(dep in services) && !names.includes(dep)) { missingDeps.add(dep); } }) ); const depNames = [...missingDeps].join(","); throw new Error( `Some services could not be started: ${names}. Missing dependencies: ${depNames}` ); }如果toStart.size >0 ,说明这里面的服务的依赖想没有得到满足,所以无法加载,会抛出一个Error
5、附录:odoo17 env.js /** @odoo-module **/ import { registry } from"./core/registry"; import { EventBus } from"@odoo/owl"; // ----------------------------------------------------------------------------- // Types // ----------------------------------------------------------------------------- /** * @typedef {Object} OdooEnv * @property {import("services").Services} services * @property {EventBus} bus * @property {string} debug * @property {(str: string) => string} _t * @property {boolean} [isSmall] */ // ----------------------------------------------------------------------------- // makeEnv // ----------------------------------------------------------------------------- /** * Return a value Odoo Env object * * @returns {OdooEnv} */ export function makeEnv() { return { bus: new EventBus(), services: {}, debug: odoo.debug, get isSmall() { throw new Error("UI service not initialized!"); }, }; } // ----------------------------------------------------------------------------- // Service Launcher // ----------------------------------------------------------------------------- const serviceRegistry = registry.category("services"); export const SERVICES_METADATA = {}; let startServicesPromise = null; /** * Start all services registered in the service registry, while making sure * each service dependencies are properly fulfilled. * * @param {OdooEnv} env * @returns {Promise