odoo16前端框架源码阅读——env.js

news/2024/7/7 22:16:36

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个属性:

  1. bus: 全局数据总线
  2. services:全局服务对象
  3. debug: 是否debug模式
  4. 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<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.
    debugger
    await 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 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);
            } 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;
    }
}


http://lihuaxi.xjx100.cn/news/1799548.html

相关文章

【Effective C++ 笔记】(四)设计与声明

【四】设计与声明 条款18 &#xff1a; 让接口容易被正确使用&#xff0c;不易被误用 Item 18: 让接口容易被正确使用&#xff0c;不易被误用 Make interfaces easy to use correctly and hard to use incorrectly. “让接口容易被正确使用&#xff0c;不易被误用”&#xff0…

CopyOnWriteArrayList 源码详解

目录 一. 前言 二. 源码详解 2.1. 类结构 2.2. 属性 2.3. 构造方法 2.4. add(E e) 2.5. add(int index, E element) 2.6. addIfAbsent() 方法 2.7. 获取元素() 方法 2.8. remove(int index) 2.9. size() 三. FAQ 3.1. 为什么CopyOnWriteArrayList没有size属性&…

linux rm 删除找回的几种方法

对于rm,很多人都有惨痛的教训。我也遇到一次&#xff0c;一下午写的程序就被rm掉了&#xff0c;幸好只是一个文件&#xff0c;第二天很快又重新写了一遍。但是很多人可能就不像我这么幸运了。本文收集了一些在Linux下恢复rm删除的文件的方法&#xff0c;给大家作为参考。 1.几…

python趣味编程-5分钟实现一个Flappy Bird游戏(含源码、步骤讲解)

Python 中的 Flappy Bird 游戏可以免费下载开源代码,它是为想要学习 Python 的初学者创建的。 该项目系统使用了 Pygame 和 Random 模块。 Pygame 是一组跨平台的 Python 模块,专为编写视频游戏而设计。 Python 中的 Flappy Bird 代码 – 项目信息 项目名称:Python 中的 Fl…

bat随手记

目录 bat批处理常用命令查询有哪些reg命令&#xff0c;帮助信息——reg /?查询注册表信息——reg query /?切换到批处理文件目录——cd /d "%~dp0"永久设置环境变量——setx命令设置注册表内容——/v名称&#xff0c;/t类型&#xff0c;/d数据%cd%和%~dp0的区别/f没…

第十九章,Java绘图

Graphics类 Graphics类是所有图形上下文的抽象基本类&#xff0c;它允许应用程序在组件以及闭屏图像上进行绘制 Graphics类封装了Java支持的基本绘图操作所需的状态信息&#xff0c;主要包括颜色、字体、画笔、文本、图像等 Graphics类提供了常用的绘图方法&#xff0c;利用这些…

Ubuntu20.04安装Beyond Compare 4.4.7

参考链接&#xff1a; 1.Ubuntu20.04 Beyond Compare 4.3.7 安装 2.Ubuntu20.04安装Beyond Compare 4.3.7

关于响应式编程ReactiveX,RxGo

ReactiveX&#xff0c;简称为 Rx&#xff0c;是一个异步编程的 API。与 callback&#xff08;回调&#xff09;、promise&#xff08;JS 提供这种方式&#xff09;和 deferred&#xff08;Python 的 twisted 网络编程库就是使用这种方式&#xff09;这些异步编程方式有所不同&a…