邯鄲做網(wǎng)站推廣找誰國內(nèi)廣告聯(lián)盟平臺
env.js(env的初始化以及服務(wù)的加載)
路徑:addons\web\static\src\env.js
這個文件的作用就是初始化env,主要是加載所有的服務(wù)。如orm, title, dialog等。
1、env.js 的加載時機
前文我們講過前端的啟動函數(shù),start.js,其中有這么兩句,這里有兩個函數(shù)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: 全局?jǐn)?shù)據(jù)總線
- services:全局服務(wù)對象
- debug: 是否debug模式
- isSmall:判斷手機端還是移動端
這里拋出一個異常是為了在后面會覆蓋這個方法。
從中可以看出,env最重要的其實就兩個對象,一個是全局?jǐn)?shù)據(jù)總線,負(fù)責(zé)組件之間通信,另一個就是service。
3、startServices
啟動所有在注冊表中注冊的服務(wù),并且保證所有的依賴都得到滿足。
首先獲取所有在注冊表中注冊的服務(wù)serviceRegistry
初始化元數(shù)據(jù)SERVICES_METADATA, 主要是保存service中sync的內(nèi)容,有什么作用還不清楚
主角開始上場: startServices
啟動所有在注冊表中注冊的服務(wù),并且保證每個服務(wù)的依賴都得到滿足。
await Promise.resolve(); // 等待之前的異步操作都完成const toStart = new Set(); // 初始化要啟動的服務(wù),因為每個服務(wù)都不同,所以用了setserviceRegistry.addEventListener // 這段沒看懂,這里添加了一個事件監(jiān)聽,動態(tài)增加注冊服務(wù)的await _startServices(env, toStart); // 將env和toStart對象傳入,干正事了
4._startServices(env, toStart)
4.1 填充 toStart 集合
const services = env.services; //獲取了env中的service對象,這時候還是空的,啥也沒有// 根據(jù)注冊表中的服務(wù)信息,初始化了toStart集合,里面都是創(chuàng)建好的實例對象,并且有一個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()
關(guān)鍵的地方來了, 這里有一個關(guān)鍵的函數(shù)findNext(),先看看它的定義:
// 從toStart集合中遍歷,有過集合中的服務(wù)有依賴并且依賴都已經(jīng)滿足了,則返回這個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 、啟動服務(wù),并填充到env的services中
在start函數(shù)中,findNext函數(shù)返回的服務(wù),第一步要最的就是從toStart 刪除,這樣遍歷的次數(shù)會越來越少,我看過,odoo17一共有69個服務(wù),while一共還要遍歷69次,每次加載一個服務(wù)。 通過這種方式,很好的處理了服務(wù)之間的依賴關(guān)系,并且最大限度的實現(xiàn)了并行。
// start as many services in parallel as possible 并行啟動盡可能多的服務(wù)async function start() {let service = null;const proms = [];while ((service = findNext())) {const name = service.name;toStart.delete(service); // 刪除要加載的服務(wù)const entries = (service.dependencies || []).map((dep) => [dep, services[dep]]);const dependencies = Object.fromEntries(entries);let value;try {value = service.start(env, dependencies); // 調(diào)用start函數(shù),并將返回值付給value} catch (e) {value = e;console.error(e);}if ("async" in service) {SERVICES_METADATA[name] = service.async; // 保存服務(wù)的元數(shù)據(jù),后面可能會有用}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函數(shù)返回的對象。
這點很重要,每個service都要有start函數(shù),而且要有返回值。
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 ,說明這里面的服務(wù)的依賴想沒有得到滿足,所以無法加載,會拋出一個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;}
}