odoo16前端框架源码阅读——env.js
env.js(env的初始化以及服务的加载)
路径: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(); // 初始化要启动的服务,因为每个服务都不同,所以用了setserviceRegistry.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是一个Promiseproms.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<void>}*/
export async function startServices(env) {// Wait for all synchronous code so that if new services that depend on// one another are added to the registry, they're all present before we// start them regardless of the order they're added to the registry.debuggerawait Promise.resolve();const toStart = new Set();serviceRegistry.addEventListener("UPDATE", async (ev) => {// Wait for all synchronous code so that if new services that depend on// one another are added to the registry, they're all present before we// start them regardless of the order they're added to the registry.await Promise.resolve();const { operation, key: name, value: service } = ev.detail;if (operation === "delete") {// We hardly see why it would be usefull to remove a service.// Furthermore we could encounter problems with dependencies.// Keep it simple!return;}if (toStart.size) {const namedService = Object.assign(Object.create(service), { name });toStart.add(namedService);} else {await _startServices(env, toStart);}});await _startServices(env, toStart);
}async function _startServices(env, toStart) {if (startServicesPromise) {return startServicesPromise.then(() => _startServices(env, toStart));}const services = env.services;for (const [name, service] of serviceRegistry.getEntries()) {if (!(name in services)) {const namedService = Object.assign(Object.create(service), { name });toStart.add(namedService);}}// start as many services in parallel as possibleasync 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);} catch (e) {value = e;console.error(e);}if ("async" in service) {SERVICES_METADATA[name] = service.async;}if (value instanceof Promise) {proms.push(new Promise((resolve) => {value.then((val) => {services[name] = val || null;}).catch((error) => {services[name] = error;console.error("Can't load service '" + name + "' because:", error);}).finally(resolve);}));} else {services[service.name] = value || null;}}await Promise.all(proms);if (proms.length) {return start();}}startServicesPromise = start();await startServicesPromise;startServicesPromise = null;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}`);}function findNext() {for (const s of toStart) {if (s.dependencies) {if (s.dependencies.every((d) => d in services)) {return s;}} else {return s;}}return null;}
}相关文章:
odoo16前端框架源码阅读——env.js
env.js(env的初始化以及服务的加载) 路径:addons\web\static\src\env.js 这个文件的作用就是初始化env,主要是加载所有的服务。如orm, title, dialog等。 1、env.js 的加载时机 前文我们讲过前端的启动函数,start.…...
浙大恩特客户资源管理系统 SQL注入漏洞复现
0x01 产品简介 浙大恩特客户资源管理系统是一款针对企业客户资源管理的软件产品。该系统旨在帮助企业高效地管理和利用客户资源,提升销售和市场营销的效果。 0x02 漏洞概述 浙大恩特客户资源管理系统中T0140_editAction.entweb接口处存在SQL注入漏洞,未…...
ESP32网络开发实例-BME280传感器数据保存到InfluxDB时序数据库
BME280传感器数据保存到InfluxDB时序数据库 文章目录 BME280传感器数据保存到InfluxDB时序数据库1、BM280和InfluxDB介绍2、软件准备3、硬件准备4、代码实现在本文中,将详细介绍如何将BME280传感器数据上传到InfluxDB中,方便后期数据处理。 1、BM280和InfluxDB介绍 InfluxDB…...
C++中sort()函数的greater<int>()参数
目录 1 基础知识2 模板3 工程化 1 基础知识 sort()函数中的greater<int>()参数表示将容器内的元素降序排列。不填此参数,默认表示升序排列。 vector<int> a {1,2,3}; sort(a.begin(), a.end(), greater<int>()); //将a降序排列 sort(a.begin()…...
2024有哪些免费的mac苹果电脑内存清理工具?
在我们日常使用苹果电脑的过程中,随着时间的推移,可能会发现设备的速度变慢了,甚至出现卡顿的现象。其中一个常见的原因就是程序占用内存过多,导致系统无法高效地运行。那么,苹果电脑内存怎么清理呢?本文将…...
线性表的概念
目录 1.什么叫线性表2.区分线性表的题 1.什么叫线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是…...
锐捷练习-ospf虚链路及rip路由相互引入
一、相关知识补充 1、ospf基本概述 OSPF(Open Shortest Path First)是一种链路状态路由协议,用于在计算机网络中进行路由选择。它是内部网关协议(IGP)之一,常用于大规模企业网络或互联网服务提供商的网络…...
【机器学习】线性回归算法:原理、公式推导、损失函数、似然函数、梯度下降
1. 概念简述 线性回归是通过一个或多个自变量与因变量之间进行建模的回归分析,其特点为一个或多个称为回归系数的模型参数的线性组合。如下图所示,样本点为历史数据,回归曲线要能最贴切的模拟样本点的趋势,将误差降到最小。 2. 线…...
Word中NoteExpress不显示的问题
首先确认我们以及安装了word插件 我们打开word却没有。此时我们打开:文件->选项->加载项 我们发现被禁用了 选择【禁用项目】(如果没有,试一试【缓慢且禁用的加载项】),点击转到 选择启用 如果没有禁用且没有出…...
连接池的大体介绍,常用配置及在springboot项目中的应用
连接池 在Java开发中,常见的数据库连接池有哪些?_java常见数据库连接池_举个例子学java的博客-CSDN博客 常见的连接池配置参数 java 连接池参数 - 百度文库 连接池的具体配法 Spring Boot之默认连接池配置策略_spring mysql默认连接池大小-CSDN博客...
Java之SpringCloud Alibaba【九】【Spring Cloud微服务Skywalking】
Java之SpringCloud Alibaba【一】【Nacos一篇文章精通系列】跳转Java之SpringCloud Alibaba【二】【微服务调用组件Feign】跳转Java之SpringCloud Alibaba【三】【微服务Nacos-config配置中心】跳转Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】跳转Java之Sprin…...
wpf devexpress设置行和编辑器
如下教程示范如何计算行布局,特定的表格单元编辑器,和格式化显示值。这个教程基于前一个文章 选择行显示 GridControl为所有字段生成行和绑定数据源,如果AutoGenerateColumns 属性选择AddNew。添加行到GridControl精确显示为特别的几行设置。…...
AdaBoost 算法:理解、实现和掌握 AdaBoost
一、介绍 Boosting 是一种集成建模技术,由 Freund 和 Schapire 于 1997 年首次提出。从那时起,Boosting 就成为解决二元分类问题的流行技术。这些算法通过将大量弱学习器转换为强学习器来提高预测能力 。 Boosting 算法背后的原理是,我们首先…...
基于ssm+vue设备配件检修管理系统
摘要 随着工业设备的日益复杂和多样化,设备配件的检修管理成为保障生产运行和设备寿命的关键环节。本研究基于SSM框架(Spring Spring MVC MyBatis),致力于设计和实现一套全面、高效的设备配件检修管理系统。该系统不仅能够提高设…...
Reids集群
目录 一、集群的概念 1.为什么要搭建集群? 2.Redis搭建集群是否需要考虑状态同步的问题? 二、Redis集群的模式 1.redis集群--主从模式 1.1什么是Redis的主从模式? 1.2.主从模式它们之间的数据是怎么实现一个同步的? 1.3.主…...
自定义指令基础
除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives) 选项式API_自定义指令 <template><h3>自定义指令</h3><p v-author>文本信息</p> </template> <script> e…...
软考-高级-系统架构设计师教程(清华第2版)【第5章 软件工程基础知识(190~233)-思维导图】
软考-高级-系统架构设计师教程(清华第2版)【第5章 软件工程基础知识(190~233)-思维导图】 课本里章节里所有蓝色字体的思维导图...
Oneid方案
一、前文 用户画像的前提是标识出用户,存在以下场景:不同业务系统对同一个人的标识,匿名用户行为的行为归因;本文提供多种解决方案,提供大家思考。 二、方案矩阵 三、其他 相关连接: 如何通过图算法能力获…...
【超好用的工具库】hutool-all工具库的基本使用
简介(可不看): hutool-all是一个Java工具库,提供了许多实用的工具类和方法,用于简化Java开发过程中的常见任务。它包含了各种模块,涵盖了字符串操作、日期时间处理、加密解密、文件操作、网络通信、图片处…...
趣学python编程 (一、计算机基础知识科普)
未来是高度科技化和智能化的时代。过去不识字的叫“文盲”,如今不懂点计算机知识,则可能是新时代的“文盲”。不论从事什么行业,了解下计算机和编程都是有益的。Python 连续多年占据最受欢迎的编程语言榜首,未来Python有机会成为像…...
如何在KubeOperator中选择最佳存储方案:NFS、Ceph RBD和Local Volume完全指南
如何在KubeOperator中选择最佳存储方案:NFS、Ceph RBD和Local Volume完全指南 【免费下载链接】KubeOperator KubeOperator 是一个开源的轻量级 Kubernetes 发行版,专注于帮助企业规划、部署和运营生产级别的 K8s 集群。 项目地址: https://gitcode.co…...
5分钟快速部署:docker-elk实时数据处理架构完整指南 [特殊字符]
5分钟快速部署:docker-elk实时数据处理架构完整指南 🚀 【免费下载链接】docker-elk deviantony/docker-elk: 是一个使用 Docker 部署的 ELK Stack(Elasticsearch、Logstash 和 Kibana)解决方案,提供了预先构建的 Dock…...
Qwen2.5-7B离线推理实战:无需GPU,CPU版本快速部署指南
Qwen2.5-7B离线推理实战:无需GPU,CPU版本快速部署指南 1. 引言 在当今AI技术快速发展的背景下,大型语言模型的应用越来越广泛。然而,许多开发者和企业在实际部署时面临GPU资源不足或成本过高的问题。本文将介绍如何在普通CPU环境…...
OpenClaw插件系统:让AI能力无限扩展
前言 前面入我们已经掌握了OpenClaw的基本用法:安装部署、飞书接入、人设配置、Skill扩展。 但如果你想让OpenClaw接入更多平台、集成更多能力怎么办? 答案是:插件系统。 插件是OpenClaw的核心扩展机制。通过插件,你可以&…...
Java突变测试终极指南:Pitest如何提升你的代码质量
Java突变测试终极指南:Pitest如何提升你的代码质量 【免费下载链接】pitest State of the art mutation testing system for the JVM 项目地址: https://gitcode.com/gh_mirrors/pi/pitest 突变测试是Java开发中革命性的质量保障技术,而Pitest&am…...
Windows右键菜单管理终极指南:3分钟打造高效桌面操作环境
Windows右键菜单管理终极指南:3分钟打造高效桌面操作环境 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾因Windows右键菜单过于臃肿而烦恼&…...
AD23导出Gerber文件保姆级教程:从PCB到嘉立创下单,新手避坑指南
AD23导出Gerber文件全流程实战:从设计检查到嘉立创安全下单 第一次将精心设计的PCB转化为可生产的Gerber文件,就像新手司机首次独立上路——每个操作都可能隐藏着意想不到的陷阱。作为使用Altium Designer 23(AD23)的设计师&…...
这家口腔机构,如何用AI把到院成本从1200+打到310元?
广东有一家口腔机构,三级专科,种植体量在区域排前三。 听起来很牛吧?但老板跟我聊天的时候,愁得不行。他说,抖音投放成本飘高,线索到院率低,客服人手不足,加微后无差别群发ÿ…...
立知lychee-rerank-mm快速上手:无需代码,网页界面轻松实现文档相关性打分
立知lychee-rerank-mm快速上手:无需代码,网页界面轻松实现文档相关性打分 你是不是经常遇到这样的困扰?在搜索引擎里输入一个问题,结果返回的答案五花八门,真正有用的信息却藏在好几页之后。或者,你的智能…...
零成本上线个人博客:我的阿贝云免费云服务器实战记录
作为一名学生党/程序员,你是否也想拥有一台属于自己的云服务器,用来搭建博客、跑 Demo、练习 Linux?但又被各大云厂商的“首月0元”、“学生认证”等套路劝退?前段时间,我无意间发现了 阿贝云 的 永久免费云服务器 和 …...
