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有机会成为像…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
