Skip to content

Commit 2fa4ae3

Browse files
committed
[wip] async support
1 parent d19ef04 commit 2fa4ae3

File tree

5 files changed

+385
-7
lines changed

5 files changed

+385
-7
lines changed

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,87 @@ webview.navigate(f"data:text/html,{quote(html)}")
150150
webview.run()
151151
```
152152

153+
### Async Python Functions with JavaScript:
154+
155+
Webview Python supports binding asynchronous Python functions that can be called from JavaScript. This is useful for time-consuming operations that should not block the main thread.
156+
157+
```python
158+
import asyncio
159+
from webview.webview import Webview, Size, SizeHint
160+
161+
webview = Webview(debug=True)
162+
163+
# Async Python function that can be called from JavaScript
164+
async def delayed_message(message, delay=1):
165+
# Simulating a time-consuming operation
166+
await asyncio.sleep(delay)
167+
return f"Async response after {delay}s: {message}"
168+
169+
# Async function with progress reporting
170+
async def process_with_progress(steps=5, step_time=1):
171+
results = []
172+
for i in range(1, steps + 1):
173+
await asyncio.sleep(step_time)
174+
# Report progress to JavaScript
175+
progress = (i / steps) * 100
176+
webview.eval(f"updateProgress({progress}, 'Processing: Step {i}/{steps}')")
177+
results.append(f"Step {i} completed")
178+
179+
return {
180+
"status": "complete",
181+
"steps": steps,
182+
"results": results
183+
}
184+
185+
# Bind async Python functions
186+
webview.bind("delayedMessage", delayed_message)
187+
webview.bind("processWithProgress", process_with_progress)
188+
189+
# HTML/JavaScript
190+
html = """
191+
<html>
192+
<head>
193+
<script>
194+
async function callAsyncPython() {
195+
try {
196+
document.getElementById('result').innerHTML = "Waiting for async response...";
197+
const result = await delayedMessage("Hello from async world!", 2);
198+
document.getElementById('result').innerHTML = result;
199+
} catch (err) {
200+
document.getElementById('result').innerHTML = `Error: ${err}`;
201+
}
202+
}
203+
204+
function updateProgress(percent, message) {
205+
document.getElementById('progress').style.width = percent + '%';
206+
document.getElementById('progress-text').textContent = message;
207+
}
208+
</script>
209+
</head>
210+
<body>
211+
<button onclick="callAsyncPython()">Call Async Python</button>
212+
<div id="result"></div>
213+
<div id="progress" style="background-color: #ddd; width: 100%">
214+
<div id="progress-bar" style="height: 20px; background-color: #4CAF50; width: 0%"></div>
215+
</div>
216+
<div id="progress-text"></div>
217+
</body>
218+
</html>
219+
"""
220+
221+
webview.navigate(f"data:text/html,{quote(html)}")
222+
webview.run()
223+
```
224+
225+
For a more complete example, see [bind_in_local_async.py](examples/bind_in_local_async.py) and [bind_in_local_async.html](examples/bind_in_local_async.html) in the examples directory.
226+
153227
## Features
154228

155229
- Create desktop applications using HTML, CSS, and JavaScript
156230
- Load local HTML files or remote URLs
157231
- Bidirectional Python-JavaScript communication
232+
- Support for async Python functions with JavaScript promises
233+
- Progress reporting for long-running tasks
158234
- Window size and title customization
159235
- Debug mode for development
160236
- Cross-platform support (Windows, macOS, Linux)
@@ -261,6 +337,7 @@ python -m build -n -w
261337

262338
- [x] Publish to PyPI
263339
- [x] Setup GitHub Actions for CI/CD
340+
- [x] Add async function support
264341
- [ ] Add preact example
265342
- [ ] Add three.js example
266343
- [ ] Add three.js fiber example
@@ -271,7 +348,11 @@ python -m build -n -w
271348
## References
272349

273350
- [Webview](https://github.com/webview/webview)
351+
- [webview C API IMPL](https://github.com/webview/webview/blob/master/core/include/webview/c_api_impl.hh)
352+
- [Webview C API](https://github.com/webview/webview/blob/master/src/webview.h)
274353
- [webview_deno](https://github.com/eliassjogreen/webview_deno)
354+
- [asyncio-guest](https://github.com/congzhangzh/asyncio-guest)
355+
- [asyncio-guest win32](https://github.com/congzhangzh/asyncio-guest/blob/master/asyncio_guest/asyncio_guest_win32.py)
275356

276357
# License
277358

examples/bind_in_local_async.html

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<!DOCTYPE html>
2+
<html lang="zh">
3+
<head>
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
5+
<title>Python-JavaScript 异步绑定演示</title>
6+
<style>
7+
body {
8+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9+
margin: 20px;
10+
line-height: 1.6;
11+
}
12+
.container {
13+
max-width: 760px;
14+
margin: 0 auto;
15+
}
16+
.demo-section {
17+
margin-bottom: 30px;
18+
padding: 20px;
19+
border: 1px solid #ddd;
20+
border-radius: 5px;
21+
background-color: #f9f9f9;
22+
}
23+
h1 {
24+
text-align: center;
25+
color: #333;
26+
}
27+
h2 {
28+
margin-top: 0;
29+
color: #444;
30+
}
31+
.result {
32+
margin-top: 15px;
33+
padding: 15px;
34+
background: #fff;
35+
border: 1px solid #eee;
36+
border-radius: 4px;
37+
min-height: 50px;
38+
}
39+
button {
40+
background-color: #4CAF50;
41+
color: white;
42+
padding: 8px 16px;
43+
border: none;
44+
border-radius: 4px;
45+
cursor: pointer;
46+
font-size: 14px;
47+
margin-right: 8px;
48+
}
49+
button:hover {
50+
background-color: #45a049;
51+
}
52+
.error {
53+
color: red;
54+
}
55+
.progress-bar {
56+
width: 100%;
57+
background-color: #e0e0e0;
58+
border-radius: 4px;
59+
margin-top: 10px;
60+
}
61+
.progress {
62+
height: 20px;
63+
background-color: #4CAF50;
64+
border-radius: 4px;
65+
width: 0%;
66+
transition: width 0.3s;
67+
}
68+
.progress-label {
69+
margin-top: 5px;
70+
font-size: 14px;
71+
color: #666;
72+
}
73+
pre {
74+
background-color: #f5f5f5;
75+
padding: 10px;
76+
border-radius: 4px;
77+
overflow-x: auto;
78+
}
79+
</style>
80+
<script>
81+
// 简单延迟响应测试
82+
async function testDelayedResponse() {
83+
const resultDiv = document.getElementById('delayed-result');
84+
resultDiv.innerHTML = '请等待...';
85+
86+
try {
87+
const seconds = parseFloat(document.getElementById('delay-seconds').value) || 1;
88+
const result = await delayedResponse(seconds);
89+
resultDiv.innerHTML = result;
90+
} catch (err) {
91+
resultDiv.innerHTML = `<span class="error">错误: ${err}</span>`;
92+
}
93+
}
94+
95+
// 进度报告测试
96+
async function testProgress() {
97+
const resultDiv = document.getElementById('progress-result');
98+
const progressBar = document.getElementById('progress-bar');
99+
const progressLabel = document.getElementById('progress-label');
100+
101+
resultDiv.innerHTML = '任务执行中...';
102+
progressBar.style.width = '0%';
103+
progressLabel.textContent = '0%';
104+
105+
try {
106+
const steps = parseInt(document.getElementById('progress-steps').value) || 5;
107+
const stepTime = parseFloat(document.getElementById('step-time').value) || 0.5;
108+
109+
const result = await processWithProgress(steps, stepTime);
110+
resultDiv.innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
111+
} catch (err) {
112+
resultDiv.innerHTML = `<span class="error">错误: ${err}</span>`;
113+
}
114+
}
115+
116+
// 用于从Python更新进度条
117+
function updateProgress(percent, message) {
118+
const progressBar = document.getElementById('progress-bar');
119+
const progressLabel = document.getElementById('progress-label');
120+
121+
progressBar.style.width = `${percent}%`;
122+
progressLabel.textContent = `${Math.round(percent)}% - ${message}`;
123+
}
124+
125+
// 模拟API请求测试
126+
async function testFetchData() {
127+
const resultDiv = document.getElementById('fetch-result');
128+
resultDiv.innerHTML = '获取数据中...';
129+
130+
try {
131+
const delay = parseFloat(document.getElementById('fetch-delay').value) || 2;
132+
const success = document.getElementById('fetch-success').checked;
133+
134+
const result = await fetchData(delay, success);
135+
resultDiv.innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
136+
} catch (err) {
137+
resultDiv.innerHTML = `<span class="error">错误: ${err}</span>`;
138+
}
139+
}
140+
</script>
141+
</head>
142+
<body>
143+
<div class="container">
144+
<h1>Python-JavaScript 异步绑定演示</h1>
145+
146+
<!-- 简单延迟响应 -->
147+
<div class="demo-section">
148+
<h2>简单延迟响应</h2>
149+
<p>测试基本的异步函数,带有延迟响应</p>
150+
151+
<div>
152+
<label for="delay-seconds">延迟时间(秒): </label>
153+
<input type="number" id="delay-seconds" min="0.1" step="0.1" value="1">
154+
</div>
155+
156+
<div style="margin-top: 10px;">
157+
<button onclick="testDelayedResponse()">测试延迟响应</button>
158+
</div>
159+
160+
<div class="result" id="delayed-result">
161+
结果将显示在这里
162+
</div>
163+
</div>
164+
165+
<!-- 进度报告 -->
166+
<div class="demo-section">
167+
<h2>异步进度报告</h2>
168+
<p>通过JavaScript回调函数报告异步操作的进度</p>
169+
170+
<div>
171+
<label for="progress-steps">步骤数: </label>
172+
<input type="number" id="progress-steps" min="1" value="5">
173+
174+
<label for="step-time" style="margin-left: 10px;">每步时间(秒): </label>
175+
<input type="number" id="step-time" min="0.1" step="0.1" value="0.5">
176+
</div>
177+
178+
<div style="margin-top: 10px;">
179+
<button onclick="testProgress()">开始任务</button>
180+
</div>
181+
182+
<div class="progress-bar">
183+
<div class="progress" id="progress-bar"></div>
184+
</div>
185+
<div class="progress-label" id="progress-label">0%</div>
186+
187+
<div class="result" id="progress-result">
188+
结果将显示在这里
189+
</div>
190+
</div>
191+
192+
<!-- 模拟API请求 -->
193+
<div class="demo-section">
194+
<h2>模拟异步API请求</h2>
195+
<p>测试异步API请求和错误处理</p>
196+
197+
<div>
198+
<label for="fetch-delay">延迟时间(秒): </label>
199+
<input type="number" id="fetch-delay" min="0.1" step="0.1" value="2">
200+
201+
<label style="margin-left: 10px;">
202+
<input type="checkbox" id="fetch-success" checked> 请求成功
203+
</label>
204+
</div>
205+
206+
<div style="margin-top: 10px;">
207+
<button onclick="testFetchData()">获取数据</button>
208+
</div>
209+
210+
<div class="result" id="fetch-result">
211+
结果将显示在这里
212+
</div>
213+
</div>
214+
</div>
215+
</body>
216+
</html>

examples/bind_in_local_async.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import os
2+
import asyncio
3+
from webview import Webview, SizeHint, Size
4+
5+
# Create webview instance
6+
webview = Webview(debug=True)
7+
8+
# 异步函数示例 - 简单延迟响应
9+
async def delayed_response(seconds=1):
10+
await asyncio.sleep(seconds)
11+
return f"异步响应完成,耗时 {seconds} 秒"
12+
13+
# 异步函数示例 - 模拟进度报告
14+
async def process_with_progress(steps=5, step_time=1):
15+
results = []
16+
for i in range(1, steps + 1):
17+
await asyncio.sleep(step_time)
18+
# 通过JavaScript回调报告进度
19+
progress = (i / steps) * 100
20+
webview.eval(f"updateProgress({progress}, '处理中: 步骤 {i}/{steps}')")
21+
results.append(f"步骤 {i} 完成")
22+
23+
return {
24+
"status": "完成",
25+
"steps": steps,
26+
"results": results
27+
}
28+
29+
# 异步函数示例 - 模拟API请求
30+
async def fetch_data(delay=2, success=True):
31+
await asyncio.sleep(delay)
32+
33+
if not success:
34+
raise Exception("模拟的API请求失败")
35+
36+
return {
37+
"id": 123,
38+
"name": "示例数据",
39+
"timestamp": "2023-06-15T12:34:56Z",
40+
"items": [
41+
{"id": 1, "value": "项目 1"},
42+
{"id": 2, "value": "项目 2"},
43+
{"id": 3, "value": "项目 3"}
44+
]
45+
}
46+
47+
# Bind Python functions
48+
webview.bind("delayedResponse", delayed_response)
49+
webview.bind("processWithProgress", process_with_progress)
50+
webview.bind("fetchData", fetch_data)
51+
52+
webview.title = "Python-JavaScript 异步绑定演示"
53+
webview.size = Size(800, 600, SizeHint.FIXED)
54+
55+
# Get the absolute path to the HTML file
56+
current_dir = os.path.dirname(os.path.abspath(__file__))
57+
html_path = os.path.join(current_dir, 'bind_in_local_async.html')
58+
59+
# Load the local HTML file
60+
webview.navigate(f"file://{html_path}")
61+
62+
# Run the webview
63+

src/webview/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .webview import Webview, Size, SizeHint

0 commit comments

Comments
 (0)