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

2023-11-09 22:54

本文主要是介绍odoo16前端框架源码阅读——rpc_service.js,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

先介绍点背景知识,这样方便阅读代码。

一、 JSONRPC的规范

https://www.jsonrpc.org/specification

中文翻译版本:https://wiki.geekdream.com/Specification/json-rpc_2.0.html

JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议。 本规范主要定义了一些数据结构及其相关的处理规则。它允许运行在基于socket,http等诸多不同消息传输环境的同一进程中。其使用JSON(RFC 4627)作为数据格式。

它为简单而生!

由于JSON-RPC使用JSON,它具有与其相同的类型系统(见http://www.json.org或RFC 4627)。JSON可以表示四个基本类型(String、Numbers、Booleans和Null)和两个结构化类型(Objects和Arrays)。 规范中,术语“Primitive”标记那4种原始类型,“Structured”标记两种结构化类型。任何时候文档涉及JSON数据类型,第一个字母都必须大写:Object,Array,String,Number,Boolean,Null。包括True和False也要大写。

1、请求对象

发送一个请求对象至服务端代表一个rpc调用, 一个请求对象包含下列成员:

jsonrpc

指定JSON-RPC协议版本的字符串,必须准确写为“2.0”

method

包含所要调用方法名称的字符串,以rpc开头的方法名,用英文句号(U+002E or ASCII 46)连接的为预留给rpc内部的方法名及扩展名,且不能在其他地方使用。

params

调用方法所需要的结构化参数值,该成员参数可以被省略。

id

已建立客户端的唯一标识id,值必须包含一个字符串、数值或NULL空值。如果不包含该成员则被认定为是一个通知。该值一般不为NULL[1],若为数值则不应该包含小数[2]。

服务端必须回答相同的值如果包含在响应对象。 这个成员用来两个对象之间的关联上下文。

没有包含“id”成员的请求对象为通知, 作为通知的请求对象表明客户端对相应的响应对象并不感兴趣

    const data = {id: rpcId,jsonrpc: "2.0",method: "call",params: params,};

2、响应对象

当发起一个rpc调用时,除通知之外,服务端都必须回复响应。响应表示为一个JSON对象,使用以下成员:

jsonrpc

指定JSON-RPC协议版本的字符串,必须准确写为“2.0”

result

该成员在成功时必须包含。

当调用方法引起错误时必须不包含该成员。

服务端中的被调用方法决定了该成员的值。

error

该成员在失败是必须包含。

当没有引起错误的时必须不包含该成员。

该成员参数值必须为5.1中定义的对象。

id

该成员必须包含。

该成员值必须于请求对象中的id成员值一致。

若在检查请求对象id时错误(例如参数错误或无效请求),则该值必须为空值。

响应对象必须包含result或error成员,但两个成员必须不能同时包含。

3、错误对象

当一个rpc调用遇到错误时,返回的响应对象必须包含错误成员参数,并且为带有下列成员参数的对象:

code

使用数值表示该异常的错误类型。 必须为整数。

message

对该错误的简单描述字符串。 该描述应尽量限定在简短的一句话。

data

包含关于错误附加信息的基本类型或结构化类型。该成员可忽略。 该成员值由服务端定义(例如详细的错误信息,嵌套的错误等)。

code	message	meaning
-32700	Parse error语法解析错误	服务端接收到无效的json。该错误发送于服务器尝试解析json文本
-32600	Invalid Request无效请求	发送的json不是一个有效的请求对象。
-32601	Method not found找不到方法	该方法不存在或无效
-32602	Invalid params无效的参数	无效的方法参数。
-32603	Internal error内部错误	JSON-RPC内部错误。
-32000 to -32099	Server error服务端错误	预留用于自定义的服务器错误。

二、rpc_service.js

路径:addons\web\static\src\core\network\rpc_service.js

1、引入相关模块, 新建了相关的Error类

导入了两个js模块, browser估计是跟浏览器相关

registry 是前端的注册表

然后是定义了四个Error继承自己标准类Error

  • RPCError
  • ConnectionLostError
  • ConnectionAbortedError
  • HTTPError
/** @odoo-module **/import { browser } from "../browser/browser";
import { registry } from "../registry";// -----------------------------------------------------------------------------
// Errors
// -----------------------------------------------------------------------------
export class RPCError extends Error {constructor() {super(...arguments);this.name = "RPC_ERROR";this.type = "server";this.code = null;this.data = null;this.exceptionName = null;this.subType = null;}
}export class ConnectionLostError extends Error {}export class ConnectionAbortedError extends Error {}export class HTTPError extends Error {}

2、Error对象

根据响应值来返回一个RPCError,这句结构赋值挺有意思

const { code, data: errorData, message, type: subType } = reponse;

我猜是吧reponse中的4个属性 code,data,message,type 分别赋值给了code,errorData,message,subType,有两个变量名称做了替换。有啥必要吗?

// -----------------------------------------------------------------------------
// Main RPC method
// -----------------------------------------------------------------------------
export function makeErrorFromResponse(reponse) {// Odoo returns error like this, in a error field instead of properly// using http error codes...const error = new RPCError();error.exceptionName = errorData.name;error.subType = subType;error.data = errorData;error.message = message;error.code = code;return error;
}

3、定义相关变量

5个输入参数

  1. env 前端环境,似乎在前端js代码中,直接可以使用这个对象。
  2. rpcId id, 为了跟响应对应起来,所以需要这个id
  3. url 地址
  4. params 参数值
  5. setting 默认是一个空对象
export function jsonrpc(env, rpcId, url, params, settings = {}) {const bus = env.bus;const XHR = browser.XMLHttpRequest;const data = {id: rpcId,jsonrpc: "2.0",method: "call",params: params,};const request = settings.xhr || new XHR();let rejectFn;

参数看着有点多,其实在后面封装成service的时候, 前两个参数是不用传的,只需要传递后面三个就行, 其实params和setting都可以不用传,必须要传的只有url。

bus: 总线, 这里要通过总线发送一些信号,rpc毕竟是远程调用,难免有意外情况发生,所以需要bus进行通信。

XHR: 浏览器发起request请求

data: 标准的jsonrpc数据格式

rejectFn, 这里值得一提,在这里声明这个变量,但是并没有赋值,是为了后面使用。

4、复习promise

promise是ES6引入的异步编程的新解决方案,语法上Promise是一个构造函数,用来封装异步操作并可以获取成功或失败的结果。

// new Promise 生成一个异步任务,参数是具体执行任务的函数,接收两个参数
// 一般叫resolve和reject都是函数,异步任务执行成功调用前者,否则调用后者
// 这两个方法将改变p对象的状态,同时给下一步处理传递数据
// 然后调用p.then  ,接收两个函数型参数,分别对应异步任务成功的回调和失败的回调
const p = new Promise(function(resolve,reject){setTimeout(function () {// let data="数据库中的用户数据";// resolve(data);let err="数据读取失败";reject(err);},1000);
})p.then(function(value){console.log(value);
},function(reason){console.error(reason);
})// 其实promise解决的也是回调地狱,嵌套过多的问题,将异步任务封装成对象了

其实,promise分了两步来完成,对象本身只是执行了一个异步任务,并没有处理异步返回的结果。 异步任务返回的结果是在p.then函数中处理的。
异步任务的返回值会影响promise对象本身的状态,会决定p.then中执行哪个回调函数。

另外,就是promise可以链式调用,执行串行的异步任务。

5、promise 中的Bus

在promise里面往总线里发送了很多消息

        if (!settings.silent) {bus.trigger("RPC:REQUEST", data.id);}

这里既然发了,那么就一定有地方接收,在odoo中搜索一下RPC:REQUEST

addons\web\static\src\webclient\loading_indicator\loading_indicator.js

这个文件代码不长,直接贴过来好了。注释中大概的意思是:

加载指示器:

当用户执行一个动作,最好是给他一些反馈说明当前有些事情正在发生。 这个指示器的作用就是在屏幕的右下角显示一个小的矩形框,里面有Loading字样,并且还有rpc的id。 3秒之后,如果rpc依然没有完成,我们将阻塞整个UI。 回头测试一下。

/** @odoo-module **/import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { Transition } from "@web/core/transition";import { Component, onWillDestroy, useState } from "@odoo/owl";/*** Loading Indicator** When the user performs an action, it is good to give him some feedback that* something is currently happening.  The purpose of the Loading Indicator is to* display a small rectangle on the bottom right of the screen with just the* text 'Loading' and the number of currently running rpcs.** After a delay of 3s, if a rpc is still not completed, we also block the UI.*/
export class LoadingIndicator extends Component {setup() {this.uiService = useService("ui");this.state = useState({count: 0,show: false,});this.rpcIds = new Set();this.shouldUnblock = false;this.startShowTimer = null;this.blockUITimer = null;this.env.bus.addEventListener("RPC:REQUEST", this.requestCall.bind(this));this.env.bus.addEventListener("RPC:RESPONSE", this.responseCall.bind(this));onWillDestroy(() => {this.env.bus.removeEventListener("RPC:REQUEST", this.requestCall.bind(this));this.env.bus.removeEventListener("RPC:RESPONSE", this.responseCall.bind(this));});}requestCall({ detail: rpcId }) {if (this.state.count === 0) {browser.clearTimeout(this.startShowTimer);this.startShowTimer = browser.setTimeout(() => {if (this.state.count) {this.state.show = true;this.blockUITimer = browser.setTimeout(() => {this.shouldUnblock = true;this.uiService.block();}, 3000);}}, 250);}this.rpcIds.add(rpcId);this.state.count++;}responseCall({ detail: rpcId }) {this.rpcIds.delete(rpcId);this.state.count = this.rpcIds.size;if (this.state.count === 0) {browser.clearTimeout(this.startShowTimer);browser.clearTimeout(this.blockUITimer);this.state.show = false;if (this.shouldUnblock) {this.uiService.unblock();this.shouldUnblock = false;}}}
}LoadingIndicator.template = "web.LoadingIndicator";
LoadingIndicator.components = { Transition };registry.category("main_components").add("LoadingIndicator", {Component: LoadingIndicator,
});

6、request 绑定load事件

request绑定了一个load事件,也就是请求返回的时候触发的操作

        request.addEventListener("load", () => {if (request.status === 502) {// If Odoo is behind another server (eg.: nginx)if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}reject(new ConnectionLostError());return;}let params;try {params = JSON.parse(request.response);} catch (_) {// the response isn't json parsable, which probably means that the rpc request could// not be handled by the server, e.g. PoolError('The Connection Pool Is Full')if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}return reject(new ConnectionLostError());const { error: responseError, result: responseResult } = params;if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}if (!responseError) {return resolve(responseResult);}const error = makeErrorFromResponse(responseError);reject(error);});

6.1、status = 502 是个什么鬼?

502 Bad Gateway错误是指代理或网关从上一个服务器接收到的响应无效或不完整。通常,这种情况发生在文件太大或处理速度太慢的高流量网站上。例如,当您访问一个具有高流量的网站时,您的请求将被发送到它的代理服务器。如果代理服务器在尝试访问网站时无法从上游服务器获取完整的响应,则会生成502错误代码。

502错误代码通常是由代理服务器、网关或负载均衡器等设备导致的,而不是由您的计算机或网络连接引起的。这意味着您只能为自己的网络连接做些有限的调整,但无法修复网关响应错误。

注释中也写的明白,502可能是因为使用了nginx反向代理,而 502错误是nginx和odoo通讯不佳造成的,这种情况rpc执行失败,

执行这一句

 reject(new ConnectionLostError());

6.2 解析返回值

如果返回的不是json格式的数据,也会触发错误

 try {params = JSON.parse(request.response);} catch (_) {return reject(new ConnectionLostError());}

catch 后面的这个下划线是什么鬼? 可能是并不关心发生了什么错误,只要解析错误,就调用reject

6.3 解构赋值

const { error: responseError, result: responseResult } = params;

根据jsonrpc规范, error和result 必须并且只能返回一个。

后面也做了判断

        if (!responseError) {return resolve(responseResult);}

如果没有错误,那就调用resolve,并返回。否则说明有错误发生,先生成一个error,然后调用reject

            const error = makeErrorFromResponse(responseError);reject(error);

7、request绑定 error事件

        request.addEventListener("error", () => {if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}reject(new ConnectionLostError());});

8、 该干正事了

        request.open("POST", url);request.setRequestHeader("Content-Type", "application/json");request.send(JSON.stringify(data));

三行代码:

1、用post方法请求的url,为什么不用get? 因为post更安全

2、指定了Content-Type为json, 这个很重要,如果不指定,服务器端不知道怎么解析数据

3、将data转成字符串并发送出去。(忙活半天,就是为了这句)

9、定义了promise.abort

Promise只有三种状态:pending、resolve、reject,一个异步的承诺一旦发出,经历等待(pending)后,最终只能为成功或者失败,中途无法取消(abort)。

这里定义promise.abort,注释也讲的明白,允许用户取消被忽略的rpc请求来接触对UI的阻塞并且不要显示错误。

        /*** @param {Boolean} rejectError Returns an error if true. Allows you to cancel*                  ignored rpc's in order to unblock the ui and not display an error.*/promise.abort = function (rejectError = true) {if (request.abort) {request.abort();}if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}if (rejectError) {rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));}};

10、jsonrpc

这个函数就干了一件事,定义了一个promise对象来发送rpc请求,并把它返回。

export function jsonrpc(env, rpcId, url, params, settings = {}) {const bus = env.bus;const XHR = browser.XMLHttpRequest;const data = {id: rpcId,jsonrpc: "2.0",method: "call",params: params,};const request = settings.xhr || new XHR();let rejectFn;const promise = new Promise((resolve, reject) => {rejectFn = reject;// handle successrequest.addEventListener("load", () => {});// handle failurerequest.addEventListener("error", () => {});request.open("POST", url);request.setRequestHeader("Content-Type", "application/json");request.send(JSON.stringify(data));});promise.abort = function (rejectError = true) {};return promise;
}

11、定义RPC服务

这里对jsonrpc做了进一步封装,并且注册为服务,看来每个服务都有个start函数,而且将env作为参数传进去。

// -----------------------------------------------------------------------------
// RPC service
// -----------------------------------------------------------------------------
export const rpcService = {async: true,start(env) {let rpcId = 0;return function rpc(route, params = {}, settings) {return jsonrpc(env, rpcId++, route, params, settings);};},
};registry.category("services").add("rpc", rpcService);

附录: odoo16 rpc_service.js

/** @odoo-module **/import { browser } from "../browser/browser";
import { registry } from "../registry";// -----------------------------------------------------------------------------
// Errors
// -----------------------------------------------------------------------------
export class RPCError extends Error {constructor() {super(...arguments);this.name = "RPC_ERROR";this.type = "server";this.code = null;this.data = null;this.exceptionName = null;this.subType = null;}
}export class ConnectionLostError extends Error {}export class ConnectionAbortedError extends Error {}export class HTTPError extends Error {}// -----------------------------------------------------------------------------
// Main RPC method
// -----------------------------------------------------------------------------
export function makeErrorFromResponse(reponse) {// Odoo returns error like this, in a error field instead of properly// using http error codes...const { code, data: errorData, message, type: subType } = reponse;const error = new RPCError();error.exceptionName = errorData.name;error.subType = subType;error.data = errorData;error.message = message;error.code = code;return error;
}export function jsonrpc(env, rpcId, url, params, settings = {}) {const bus = env.bus;const XHR = browser.XMLHttpRequest;const data = {id: rpcId,jsonrpc: "2.0",method: "call",params: params,};const request = settings.xhr || new XHR();let rejectFn;const promise = new Promise((resolve, reject) => {rejectFn = reject;if (!settings.silent) {bus.trigger("RPC:REQUEST", data.id);}// handle successrequest.addEventListener("load", () => {if (request.status === 502) {// If Odoo is behind another server (eg.: nginx)if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}reject(new ConnectionLostError());return;}let params;try {params = JSON.parse(request.response);} catch (_) {// the response isn't json parsable, which probably means that the rpc request could// not be handled by the server, e.g. PoolError('The Connection Pool Is Full')if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}return reject(new ConnectionLostError());}const { error: responseError, result: responseResult } = params;if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}if (!responseError) {return resolve(responseResult);}const error = makeErrorFromResponse(responseError);reject(error);});// handle failurerequest.addEventListener("error", () => {if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}reject(new ConnectionLostError());});// configure and send requestrequest.open("POST", url);request.setRequestHeader("Content-Type", "application/json");request.send(JSON.stringify(data));});/*** @param {Boolean} rejectError Returns an error if true. Allows you to cancel*                  ignored rpc's in order to unblock the ui and not display an error.*/promise.abort = function (rejectError = true) {if (request.abort) {request.abort();}if (!settings.silent) {bus.trigger("RPC:RESPONSE", data.id);}if (rejectError) {rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));}};return promise;
}// -----------------------------------------------------------------------------
// RPC service
// -----------------------------------------------------------------------------
export const rpcService = {async: true,start(env) {let rpcId = 0;return function rpc(route, params = {}, settings) {return jsonrpc(env, rpcId++, route, params, settings);};},
};registry.category("services").add("rpc", rpcService);

这篇关于odoo16前端框架源码阅读——rpc_service.js的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/378952

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能