简介:本资源是一份面向Delphi开发者(含初学者与进阶用户)的Web前端开发实战教程,聚焦TMS WEB Core框架,解决传统Delphi程序员向现代跨平台Web应用开发转型的核心痛点。教程系统覆盖框架原理、环境配置(Delphi社区版+TMS试用版即可)、项目创建(含Web应用、PWA、Electron及Miletus桌面应用)、Pas2JS编译机制、RTL运行时库、预处理器与命令行编译等关键技术,并深入对比IntraWEB、uniGUI等主流Delphi Web框架差异,辅以企业级UI设计、数据绑定与部署实践。资源为单文件PDF,共1个3.84MB文档,内容结构完整,含历史演进、架构解析、快速入门、调试技巧及90余页实操目录模块,便于按需精读与工程复用。已有5028人学习下载,是掌握Delphi全栈Web开发能力的高价值入门与进阶指南。
1. Delphi Web前端开发不是“用Delphi写网页”:TMS WEB Core 把 Object Pascal 编译成纯 HTML/JS,零二进制、免服务器端运行、一次编译全平台部署
很多人第一次看到“Delphi Web前端开发”时下意识皱眉:Delphi 不是做桌面软件的吗?写网页不该用 JavaScript 吗?——这恰恰是 TMS WEB Core 最反直觉、也最值得深挖的价值点。它不是在 Delphi 里嵌个 WebBrowser 控件,也不是用 Delphi 写后端再套个前端模板;而是通过一套自研的 Pascal-to-JavaScript 编译链(核心是 Pas2JS),把.pas文件直接翻译成标准、可读、可调试的 HTML + CSS + JavaScript,最终产物是纯静态文件:index.html、app.js、style.css,连node_modules都不需要。你用 Delphi IDE 写代码,按下 F9,生成的不是.exe,而是一整个能扔进 Nginx、Apache、GitHub Pages 甚至微信公众号后台的 Web 应用包。这意味着:没有 IIS 依赖、不需 .NET Runtime、不走 ASP.NET 管道、不碰 WebAssembly 黑匣子——所有逻辑都在浏览器里跑,数据靠 REST API 拉,状态靠前端管理。教程里反复强调“100% WEB HTML/JS 应用程序”,不是宣传话术,是技术事实。它适合三类人:老 Delphi 工程师想低成本切入 Web 领域、Lazarus 开发者需要跨平台轻量级 UI、以及被 Vue/React 构建流程折磨到怀疑人生的前端老兵——想回归“改完保存就能看效果”的朴素开发节奏。你不需要重学 JS 语法,但必须接受“Pascal 是源码,JS 是汇编”的新认知。
2. TMS WEB Core 的技术底座:Pas2JS 编译器、FNC 统一组件模型与 RTL 运行时库如何协同工作
2.1 Pas2JS 编译器:Object Pascal 到 JavaScript 的确定性翻译引擎
TMS WEB Core 的核心不是封装,而是翻译。它不依赖 V8 或 SpiderMonkey 引擎,也不生成 WebAssembly 字节码,而是用 Pascal 编写的 Pas2JS 编译器,将.pas源码逐行映射为语义等价的 JavaScript。这不是简单字符串替换,而是 AST 层级的语义保持:for i := 0 to Length(arr) - 1 do→for (let i = 0; i < arr.length; i++);try...except→try...catch;TStringList→class TStringList { ... }。关键参数控制翻译行为:
pas2js -Jc -Jm -Ji -Jr -Jd -Jl -Jt -Jw -Jx MyApp.pas-Jc: 启用 CommonJS 模块(适配 Node.js 环境,非必需)-Jm: 生成 ES6 模块(推荐,利于 Tree Shaking)-Ji: 插入源码映射(source map),调试时可直接在浏览器 DevTools 里断点 Pascal 行-Jr: 启用 RTL 运行时库(必选,提供System,SysUtils,Classes等基础单元)-Jd: 启用调试信息(含变量名、作用域,增大 JS 体积但调试友好)-Jl: 启用 RTL 日志(控制台输出TLog.Write('msg'))-Jt: 启用类型检查(生成typeof x === 'number'类型守卫)-Jw: 启用警告(如未初始化变量、无返回值函数)-Jx: 启用异常堆栈追踪(Error.stack包含 Pascal 调用链)
提示:生产环境建议用
-Jm -Jr -Jt,关闭-Jd -Jl -Jw -Jx;开发阶段务必加-Ji -Jd,否则调试等于盲人摸象。
Pas2JS 不是黑盒。它会为每个 Pascal 单元生成对应 JS 模块,例如WebForm1.pas→WebForm1.js,其中TWebForm1 = class extends TWebForm定义清晰可见。你可以在 Chrome 控制台里输入TWebForm1.prototype查看所有方法,或new TWebForm1()手动实例化——这说明它生成的是标准 JS 类,不是闭包模拟的伪类。
2.2 FNC(Framework Neutral Components):一套代码,三端复用的 UI 组件抽象层
TMS WEB Core 的 UI 组件(如TWebButton,TWebEdit)并非直接操作 DOM,而是构建在 FNC 层之上。FNC 是一套与渲染目标无关的组件模型:同一套 Pascal 接口,既可驱动 Web(HTML/CSS/JS)、又可驱动 VCL(Windows GDI)、还可驱动 FMX(跨平台原生控件)。这种设计带来两个硬收益:一是组件行为高度一致(比如TWebButton.OnClick和TButton.OnClick参数签名完全相同),二是未来迁移到桌面端时,UI 逻辑代码几乎零修改。FNC 的核心是TFNCBaseControl基类,它定义了Render,Update,Destroy三个生命周期钩子:
Render: 生成初始 HTML 字符串(如<button id="btn1" class="tms-btn">Click</button>)Update: 响应属性变更(如Caption := 'Save'→el.textContent = 'Save')Destroy: 清理事件监听、定时器、内存引用
所有TWeb*控件都继承自TFNCBaseControl,因此TWebLabel.Caption修改后,底层自动调用Update触发 DOM 更新,无需手动innerHTML = ...。这种响应式更新机制比 jQuery 手动操作更可靠,又比 React Virtual DOM 更轻量——因为它是编译期确定的细粒度更新,而非运行时 diff。
2.3 RTL(Run-Time Library):让 Pascal 习惯无缝迁移到浏览器的运行时支撑
传统 Delphi RTL(如System,SysUtils)为 Win32 设计,大量依赖 Windows API。TMS WEB Core 提供了专为浏览器环境裁剪的 RTL,位于TMSWEBCore\RTL\目录下。它重实现了关键单元:
| Pascal 单元 | 浏览器等效实现 | 关键差异 |
|---|---|---|
System | system.js | Integer映射为 JSnumber,Boolean映射为boolean,string映射为string(非ArrayBuffer) |
SysUtils | sysutils.js | FormatDateTime使用Intl.DateTimeFormat,StrToInt调用parseInt,FileExists返回false(浏览器无文件系统) |
Classes | classes.js | TList,TStringList,TMemoryStream全部基于 JS 数组/Uint8Array实现,TThread被禁用(浏览器无线程,用setTimeout/Promise替代) |
DateUtils | dateutils.js | 所有日期计算基于Date对象,DaysBetween精确到毫秒 |
注意:
TFileStream,TRegistry,TIniFile等依赖操作系统 API 的类,在 RTL 中被标记为deprecated或直接抛出EUnsupportedOperation异常。这不是缺陷,而是明确边界——TMS WEB Core 只负责前端呈现与交互,IO 和持久化必须交由后端 REST API 完成。
2.4 项目结构与构建流程:从.dpr到dist/的完整链路
一个标准 TMS WEB Core 项目目录结构如下:
MyWebApp/ ├── MyWebApp.dpr # 主程序入口(Pascal) ├── WebForm1.pas # 窗体单元(含 UI 定义与事件处理) ├── DataModule1.pas # 数据模块(封装 API 调用) ├── resources/ # 静态资源(CSS, images, fonts) │ └── style.css ├── index.html # 主页模板(由 TMS 自动生成或自定义) └── build/ # 构建输出目录(由 Pas2JS 生成) ├── app.js # 主应用 JS(含所有单元编译结果) ├── index.html # 最终部署文件(注入 app.js) └── assets/ # 图片等二进制资源(Base64 编码或独立文件)构建流程分三步:
- IDE 编译:Delphi IDE 调用
dcc32.exe编译.dpr为中间.dcu(仅校验语法,不生成 JS) - Pas2JS 转译:调用
pas2js.exe读取.dcu和 RTL,生成app.js - 资源打包:TMS 工具自动将
resources/下文件复制到build/,并更新index.html中的<script src="app.js">和<link href="style.css">
这个流程可完全命令行化,便于 CI/CD 集成。例如 GitLab CI 中的.gitlab-ci.yml片段:
build-web: image: tmswebcore-builder:latest script: - pas2js -Jm -Jr -Jt -Ji -Jd MyWebApp.dpr - cp resources/style.css build/ artifacts: paths: - build/3. 从零创建第一个 TMS WEB Core 应用:配置环境、新建项目、添加按钮并绑定点击事件
3.1 环境准备:Delphi 社区版 + TMS WEB Core 试用版安装要点
教程强调“只需免费 Delphi 社区版”,但实操中版本兼容性是第一道坎。截至 2024 年,Delphi 11 Alexandria 是当前最稳的基线版本。社区版下载地址为 Embarcadero 官网(搜索 “Delphi Community Edition”),安装时务必勾选:
- Desktop Development with Delphi(含 VCL/FMX)
- Web Development with Delphi(含 WebBroker、IntraWeb 支持,虽不用但依赖部分 RTL)
- Additional Tools(含
dcc32.exe,pas2js.exe)
TMS WEB Core 试用版需单独注册获取,安装包名为TMSWEBCore_Install.exe。安装时注意三点:
- 不要勾选 “Install for all users”:权限问题会导致 IDE 无法加载包
- 安装路径避免中文和空格:如
C:\TMS\WEB\Core安全,C:\Program Files\TMS或D:\我的项目\TMS会失败 - 安装后重启 Delphi IDE:否则组件面板不显示
TWeb*控件
验证是否成功:打开 Delphi →Component→Install Packages→ 查看已加载列表中是否有TMSWEBCore.bpl(版本号如v5.2.0.0)。若无,手动点击Add,定位到C:\TMS\WEB\Core\BPL\Win32\TMSWEBCore.bpl。
3.2 创建新项目:选择模板、设置输出路径、理解TWebApplication生命周期
启动 Delphi →File→New→Other→TMS WEB Core→Web Application。向导中关键选项:
- Application Type: 选
HTML Application(非Electron或PWA,先跑通基础) - Output Directory: 设为项目根目录下的
build/(与教程一致,避免路径混乱) - Use RTL: 勾选(启用 TMS RTL,否则
TStringList等不可用)
向导生成三个文件:MyWebApp.dpr,WebForm1.pas,index.html。打开MyWebApp.dpr,核心代码如下:
program MyWebApp; uses System.StartUpCopy, Web.WebApp, WebForm1 in 'WebForm1.pas' {WebForm1}; {$R *.res} begin Application.Initialize; Application.CreateForm(TWebForm1, WebForm1); Application.Run; end.这里Application是TWebApplication实例,其Run方法不进入消息循环,而是:
- 解析 URL 参数(如
?theme=dark) - 加载
index.html到 DOM - 实例化
TWebForm1并调用WebForm1.Show - 启动事件轮询(
requestAnimationFrame驱动)
TWebForm1继承自TWebForm,其OnCreate事件在窗体实例化后、Show前触发,是初始化数据的最佳位置。
3.3 添加 UI 控件:拖拽TWebButton并编写OnClick事件处理器
在WebForm1设计视图中,从组件面板TMS WEB Core页签拖一个TWebButton到窗体上。默认Name为WebButton1,Caption为'Button1'。双击按钮,IDE 自动生成事件框架:
procedure TWebForm1.WebButton1Click(Sender: TObject); begin // TODO: Add your code here end;现在写一段真实可用的逻辑:点击后弹出浏览器原生提示框,并修改按钮文字:
procedure TWebForm1.WebButton1Click(Sender: TObject); begin // 调用浏览器 alert(注意:这是 JS alert,非 Delphi MessageDlg) WebForm1.Window.Alert('Hello from Delphi!'); // 修改按钮 Caption(自动触发 Update,DOM 实时更新) WebButton1.Caption := 'Clicked!'; // 记录到浏览器控制台(方便调试) TLog.Write('Button clicked at ' + DateTimeToStr(Now)); end;关键点解析:
WebForm1.Window.Alert是TWebWindow类的方法,直接调用window.alert(),非阻塞主线程(与 JS 一致)WebButton1.Caption := ...触发TWebButton.Update,内部执行el.textContent = value,无需手动 DOM 操作TLog.Write输出到浏览器console.log,且带时间戳,比WriteLn更适合 Web 环境
编译运行(F9):Delphi 自动打开默认浏览器,地址为file:///C:/path/to/build/index.html。点击按钮,弹窗出现,文字变更,控制台输出日志——首战告捷。
3.4 配置index.html模板:自定义标题、引入外部 CSS、设置 viewport
index.html是 TMS 生成的入口页,但允许手动编辑。打开它,你会看到类似结构:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>TMS WEB Core Application</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div id="app"></div> <script type="text/javascript" src="app.js"></script> </body> </html>按需修改:
<title>:改为你的应用名,如<title>库存管理系统</title><meta name="viewport">:对移动端至关重要,initial-scale=1.0确保不缩放,user-scalable=no可选(禁用双指缩放)<link>:若你有自定义 CSS,确保路径正确;TMS 默认生成style.css在build/下,无需改动<div id="app">:这是 TMS 应用的挂载点,绝对不要删除或改 ID,否则TWebForm无法渲染
提示:若需引入第三方 CSS(如 Bootstrap),在
<head>中添加:<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">但注意:TMS 组件有自己的 CSS 类(如
tms-btn),与 Bootstrap 类可能冲突,建议优先用 TMS 主题系统。
4. TMS WEB Core 核心组件实战:TWebEdit、TWebListBox与TWebDateTimePicker的数据绑定与事件处理
4.1TWebEdit:单行文本输入框的双向绑定与验证模式
TWebEdit是最常用输入控件,但新手易忽略其Text属性与OnChange事件的配合逻辑。创建一个登录表单场景:用户名输入框 + 密码输入框 + 登录按钮。
设计视图拖入:
TWebEdit(Name := WebEditUser,Placeholder := '请输入用户名')TWebEdit(Name := WebEditPass,PasswordChar := '*',Placeholder := '请输入密码')TWebButton(Name := WebButtonLogin,Caption := '登录')
关键代码在WebButtonLoginClick:
procedure TWebForm1.WebButtonLoginClick(Sender: TObject); var User, Pass: string; begin User := WebEditUser.Text; Pass := WebEditPass.Text; // 基础验证(前端仅作友好提示,后端必须二次校验) if Trim(User) = '' then begin WebForm1.Window.Alert('用户名不能为空'); WebEditUser.SetFocus; // 聚焦回输入框 Exit; end; if Length(Pass) < 6 then begin WebForm1.Window.Alert('密码长度不能少于6位'); WebEditPass.SetFocus; Exit; end; // 模拟登录请求(实际应调用 REST API) TLog.Write('Login attempt: ' + User); WebButtonLogin.Caption := '登录中...'; WebButtonLogin.Enabled := False; // 模拟网络延迟(用 Promise 代替 setTimeout,更符合现代 JS) TPromise.WaitFor( function: IPromise begin Result := TPromise.New( procedure(Resolve: TPromiseResolve; Reject: TPromiseReject) begin // 此处应调用 TWebHTTPClient.Post(...) // 为简化,3秒后 resolve TWebTimer.SetTimeout( procedure begin Resolve(nil); end, 3000 ); end ); end, procedure begin WebButtonLogin.Caption := '登录成功!'; WebButtonLogin.Enabled := True; // 跳转到主页面(实际用 window.location.href) WebForm1.Window.Location.Href := 'main.html'; end, procedure(Reason: Variant) begin WebButtonLogin.Caption := '登录失败'; WebButtonLogin.Enabled := True; WebForm1.Window.Alert('网络错误,请重试'); end ); end;这里用到两个重要工具:
TPromise.WaitFor:TMS 封装的 Promise 辅助函数,避免回调地狱TWebTimer.SetTimeout:浏览器setTimeout的 Pascal 封装,参数为匿名方法
注意:
TWebEdit.Text是实时同步的,用户每敲一个键,Text属性就更新。若需防抖(Debounce),不能监听OnChange(它太频繁),而应监听OnKeyUp并用TWebTimer.SetTimeout延迟执行。
4.2TWebListBox:动态加载列表项与选中事件响应
TWebListBox用于显示可滚动的单选列表。假设从 REST API 加载用户列表:
// 在 WebForm1.OnCreate 中初始化 procedure TWebForm1.WebForm1Create(Sender: TObject); begin // 模拟从 API 获取 JSON 数据(实际用 TWebHTTPClient) var UsersJSON := '[{"id":1,"name":"张三"},{"id":2,"name":"李四"},{"id":3,"name":"王五"}]'; var Users: TJSONArray; Users := TJSONObject.ParseJSONValue(UsersJSON) as TJSONArray; // 清空现有项 WebListBox1.Clear; // 遍历 JSON,添加到 ListBox for var i := 0 to Users.Size - 1 do begin var UserObj := Users.Items[i] as TJSONObject; var Name := UserObj.GetValue<string>('name'); var ID := UserObj.GetValue<integer>('id'); // AddItem 第二个参数是 Data,可存任意对象(如整数ID) WebListBox1.AddItem(Name, Pointer(ID)); end; end;TWebListBox.AddItem的Data参数是Pointer,可存Integer,TObject, 或TJSONObject。选中事件中取出:
procedure TWebForm1.WebListBox1Select(Sender: TObject); var SelectedIndex: Integer; SelectedID: Integer; begin SelectedIndex := WebListBox1.ItemIndex; if SelectedIndex >= 0 then begin // GetItemData 返回 Pointer,需类型转换 SelectedID := Integer(WebListBox1.GetItemData(SelectedIndex)); TLog.Write('Selected user ID: ' + SelectedID.ToString); // 根据 ID 加载详情(调用 API) LoadUserDetail(SelectedID); end; end;TWebListBox支持多选(MultiSelect := True),此时ItemIndex返回首个选中项,需遍历Selected[]数组:
for var i := 0 to WebListBox1.Count - 1 do if WebListBox1.Selected[i] then TLog.Write('Selected: ' + WebListBox1.Items[i]);4.3TWebDateTimePicker:日期时间选择器的格式化与时区处理
TWebDateTimePicker默认使用浏览器本地日期控件(<input type="datetime-local">),但存在兼容性问题(Safari 不支持datetime-local)。TMS 提供了 fallback:当原生控件不可用时,自动加载轻量级 JS 日期选择器。
关键属性:
Kind:dkDate(仅日期)、dkTime(仅时间)、dkDateTime(日期+时间)Format:dfShort,dfLong,dfCustom(自定义格式,如'yyyy-mm-dd hh:nn:ss')MinDate,MaxDate: 限制可选范围(TDateTime类型)
// 设置为仅日期选择,格式为 '2024-06-15' WebDateTimePicker1.Kind := dkDate; WebDateTimePicker1.Format := dfCustom; WebDateTimePicker1.CustomFormat := 'yyyy-mm-dd'; // 设置最小日期为今天 WebDateTimePicker1.MinDate := Now; // OnChange 事件:获取选中值(TDateTime) procedure TWebForm1.WebDateTimePicker1Change(Sender: TObject); var SelectedDate: TDateTime; Formatted: string; begin SelectedDate := WebDateTimePicker1.Date; Formatted := FormatDateTime('yyyy-mm-dd', SelectedDate); TLog.Write('Selected date: ' + Formatted); // 发送到后端时,需转为 ISO 字符串(UTC 时间) // TMS 提供 TWebDateTimeHelper.ToISO8601(SelectedDate, True) → '2024-06-15T00:00:00Z' end;注意:浏览器原生
datetime-local输入框返回的是本地时区的字符串(如'2024-06-15T14:30'),而 REST API 通常要求 UTC 时间戳。TMS 的TWebDateTimeHelper.ToISO8601方法可自动处理时区转换,第二个参数True表示转为 UTC。
5. 避坑指南:TMS WEB Core 开发中 5 个高频翻车现场与血泪解决方案
5.1 现象:编译通过,但浏览器控制台报错ReferenceError: TWebForm1 is not defined
原因:Pas2JS 未正确生成WebForm1.js,常见于WebForm1.pas单元名与文件名不一致(如文件存为Form1.pas但单元声明为unit WebForm1;),或WebForm1.pas未加入项目(.dpr中uses未包含)。
解决:
- 确认
WebForm1.pas文件名与unit WebForm1;完全一致(大小写敏感) - 打开
.dpr文件,检查uses列表是否包含WebForm1 in 'WebForm1.pas' {WebForm1} - 在 Delphi IDE 中右键项目 →
Options→TMS WEB Core→Build→ 勾选Verbose output,重新编译,查看日志中是否提示Compiling WebForm1.pas
5.2 现象:TWebButton点击无反应,OnClick事件不触发
原因:TWebButton被其他 DOM 元素遮挡(如TWebPanel的ZIndex过高),或Enabled := False未重置,或事件处理器未正确关联(设计视图双击生成的事件名与.pas中声明不匹配)。
解决:
- 浏览器按
F12→Elements标签,检查按钮 DOM 元素是否被div覆盖,临时删掉父容器 CSSz-index测试 - 在
OnCreate中显式设置WebButton1.Enabled := True - 检查
.pas文件中事件声明:procedure TWebForm1.WebButton1Click(Sender: TObject);必须与对象检查器(Object Inspector)中OnClick属性值完全一致(包括大小写)
5.3 现象:TWebEdit.Text获取不到用户输入,始终为空字符串
原因:TWebEdit的Text属性在OnChange事件中读取是安全的,但在OnKeyUp或OnKeyDown中读取可能因事件顺序导致值未更新;更常见的是TWebEdit被放入TWebFrame后,Text属性访问路径错误(如Frame1.WebEdit1.Text未初始化)。
解决:
- 优先用
OnChange事件处理输入完成逻辑 - 若必须用
OnKeyUp,在事件末尾加WebEdit1.UpdateTextFromElement强制同步(TMS 内部方法) - 对于
TWebFrame,确保Frame1 := TMyFrame.Create(Self)后,再访问其子控件
5.4 现象:REST API 调用失败,TWebHTTPClient报错Network Error或CORS error
原因:浏览器同源策略限制,前端index.html通过file://协议打开时,XMLHttpRequest被禁止;或后端未设置Access-Control-Allow-Origin头。
解决:
- 绝不能用
file://测试 API:必须通过 HTTP 服务访问,如http://localhost:8080/index.html。用 Python 快速起服务:python3 -m http.server 8080 - 后端需添加 CORS 头:
Access-Control-Allow-Origin: *(开发)或Access-Control-Allow-Origin: http://localhost:8080(生产) - TMS
TWebHTTPClient默认发送Content-Type: application/json,若后端要求application/x-www-form-urlencoded,需手动设置Request.ContentType := 'application/x-www-form-urlencoded';
5.5 现象:TWebDateTimePicker在 Safari 上不显示,或日期格式错乱
原因:Safari 对datetime-local支持极差,且Format属性在不同浏览器渲染效果不一。
解决:
- 强制启用 TMS 内置日期选择器:
WebDateTimePicker1.UseNative := False; - 统一使用
dfCustom格式,避免dfShort(各浏览器解释不同) - 日期字符串传输一律用 ISO 8601(
ToISO8601),前端展示用FormatDateTime,绝不依赖浏览器toLocaleDateString
6. 进阶技巧:用TWebHTTPClient实现 RESTful 数据交互与错误重试机制
6.1TWebHTTPClient核心用法:GET/POST 请求、JSON 序列化与响应处理
TWebHTTPClient是 TMS 封装的浏览器fetchAPI,比原生XMLHttpRequest更简洁。它支持 Promise 风格,避免回调嵌套。
GET 请求获取用户列表:
procedure TWebForm1.LoadUsers; var Client: TWebHTTPClient; begin Client := TWebHTTPClient.Create; try // 配置请求头 Client.Request.Headers.Add('Accept', 'application/json'); Client.Request.Headers.Add('Authorization', 'Bearer ' + FToken); // 发送 GET 请求 Client.Get('https://api.example.com/users') .Then( procedure(Response: TWebHTTPResponse) begin if Response.StatusCode = 200 then begin // 解析 JSON 响应 var JSON := TJSONObject.ParseJSONValue(Response.Content) as TJSONObject; var Users := JSON.GetValue<TJSONArray>('data'); // 清空并填充 ListBox WebListBox1.Clear; for var i := 0 to Users.Size - 1 do begin var User := Users.Items[i] as TJSONObject; WebListBox1.AddItem(User.GetValue<string>('name'), User.GetValue<void*>('id')); end; end else WebForm1.Window.Alert('加载失败:' + Response.StatusText); end ) .Catch( procedure(Error: Variant) begin TLog.Write('GET Error: ' + Error.ToString); WebForm1.Window.Alert('网络错误,请检查连接'); end ); finally Client.Free; end; end;POST 请求提交登录数据:
procedure TWebForm1.DoLogin(const Username, Password: string); var Client: TWebHTTPClient; Payload: TJSONObject; begin Client := TWebHTTPClient.Create; try Payload := TJSONObject.Create; try Payload.AddPair('username', Username); Payload.AddPair('password', Password); Client.Request.Headers.Add('Content-Type', 'application/json'); Client.Post('https://api.example.com/login', Payload.ToJSON) .Then( procedure(Response: TWebHTTPResponse) begin if Response.StatusCode = 200 then begin var Result := TJSONObject.ParseJSONValue(Response.Content) as TJSONObject; FToken := Result.GetValue<string>('token'); WebForm1.Window.Location.Href := 'dashboard.html'; end else WebForm1.Window.Alert('登录失败:' + Response.StatusText); end ) .Catch( procedure(Error: Variant) begin TLog.Write('POST Error: ' + Error.ToString); WebForm1.Window.Alert('请求超时,请重试'); end ); finally Payload.Free; end; finally Client.Free; end; end;6.2 错误重试机制:指数退避(Exponential Backoff)与最大重试次数控制
网络请求失败很常见,硬编码Retry 3 times不够智能。TMS 没有内置重试,但可用TPromise和TWebTimer实现:
function TWebForm1.RetryGet(URL: string; MaxRetries: Integer): IPromise; var RetryCount: Integer; Client: TWebHTTPClient; begin RetryCount := 0; function TryRequest: IPromise; begin Client := TWebHTTPClient.Create; try Result := Client.Get(URL) .Then( function(Response: TWebHTTPResponse): Variant begin if Response.StatusCode in [200, 201] then Exit(Response) // 成功,结束重试 else begin Inc(RetryCount); if RetryCount >= MaxRetries then raise Exception.Create('Max retries exceeded: ' + Response.StatusText); // 指数退避:1s, 2s, 4s... var Delay := Round(Power(2, RetryCount - 1) * 1000); TLog.Write('Retry ' + RetryCount.ToString + ', delay ' + Delay.ToString + 'ms'); Exit(TPromise.New( procedure(Resolve: TPromiseResolve; Reject: TPromiseReject) begin TWebTimer.SetTimeout( procedure begin Resolve(TryRequest); end, Delay ); end )); end; end ) .Catch( function(Error: Variant): Variant begin Inc(RetryCount); if RetryCount >= MaxRetries then raise Exception.Create('Max retries exceeded: ' + Error.ToString); var Delay := Round(Power(2, RetryCount - 1) * 1000); TLog.Write('Retry ' + RetryCount.ToString + ', delay ' + Delay.ToString + 'ms'); Exit(TPromise.New( procedure(Resolve: TPromiseResolve; Reject: TPromiseReject) begin TWebTimer.SetTimeout( procedure begin Resolve(TryRequest); end, Delay ); end )); end ); except on E: Exception do begin Client <p> <a href="https://download.csdn.net/download/jelk2888/85168258" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>