fix(options): speed up chain refresh with fast path and non-blocking UI

Skip full REST tickers when WS is warm, seed subscriptions off-request, and keep the old chain visible while refreshing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-11 12:06:16 +08:00
parent 14a7adae1f
commit 6fad68f7b1
5 changed files with 153 additions and 19 deletions
+32 -12
View File
@@ -1450,8 +1450,14 @@
const uly = state.underlying;
const seq = ++chainLoadSeq;
const btn = document.getElementById("opt-load-chain");
if (btn && !soft) btn.disabled = true;
if (!soft) {
const hadChain = chainHasExpiries(state.chain) && state.chain.underlying === uly;
if (btn && !soft) {
btn.disabled = true;
if (!btn.dataset.origText) btn.dataset.origText = btn.textContent || "刷新链";
btn.textContent = "刷新中…";
}
// 已有链时不先清空表格,避免「白屏等很久」的体感
if (!soft && !hadChain) {
setExpirySelectStatus("加载到期日中…");
const tbody = document.getElementById("opt-strike-tbody");
if (tbody) {
@@ -1462,9 +1468,18 @@
try {
let d = null;
let lastMsg = "";
for (let attempt = 0; attempt < 3; attempt++) {
const expMs = (document.getElementById("opt-exp-select") || {}).value || "";
// WS 已热时走 fast,跳过最慢的整家族 REST tickers
const useFast = soft || quoteLiveWsOk || hadChain;
const maxAttempts = soft ? 2 : 3;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
if (seq !== chainLoadSeq) return;
d = await apiJson("/api/options/chain?underlying=" + encodeURIComponent(uly));
let url =
"/api/options/chain?underlying=" +
encodeURIComponent(uly) +
(useFast ? "&fast=1" : "");
if (expMs) url += "&exp_time=" + encodeURIComponent(expMs);
d = await apiJson(url);
if (seq !== chainLoadSeq) return;
if (d && d.ok && chainHasExpiries(d)) break;
lastMsg = (d && (d.msg || d.chain_error)) || "暂无到期日";
@@ -1472,14 +1487,14 @@
!!(d && d.rate_limited) ||
/50011|Too Many Requests|过于频繁/i.test(String(lastMsg || ""));
d = null;
if (attempt < 2) {
if (!soft) {
if (attempt < maxAttempts - 1) {
if (!soft && !hadChain) {
setExpirySelectStatus(
rateLimited ? "OKX 限频,稍后重试…" : "重试加载到期日…"
);
}
await new Promise(function (resolve) {
setTimeout(resolve, rateLimited ? 1200 * (attempt + 1) : 400);
setTimeout(resolve, rateLimited ? 1200 * (attempt + 1) : 300);
});
}
}
@@ -1489,6 +1504,7 @@
if (!soft) {
renderExpiries();
renderStrikes();
void watchCurrentExpiryQuotes();
}
return;
}
@@ -1510,7 +1526,7 @@
alert(friendly);
return;
}
const keepExp = soft ? (document.getElementById("opt-exp-select") || {}).value : "";
const keepExp = soft || hadChain ? (document.getElementById("opt-exp-select") || {}).value : "";
state.chain = d;
panelCache.chain = d;
panelCache.underlying = uly;
@@ -1518,7 +1534,7 @@
chainQuotedAt = Date.now();
lastChainSoftAt = chainQuotedAt;
syncAskLiqFilterFromChain(d);
if (!soft) {
if (!soft && !hadChain) {
state.selectedInst = null;
resetMoneyFilterToAll();
state.strikeExpandAll = false;
@@ -1528,18 +1544,19 @@
}
updateUnderlyingLabel();
renderExpiries();
if (soft && keepExp) {
if (keepExp) {
const sel = document.getElementById("opt-exp-select");
if (sel && Array.from(sel.options).some(function (o) { return o.value === keepExp; })) {
sel.value = keepExp;
}
}
// soft 时保留 selectedInstrenderStrikes 会先 park 再按 prevSelected 静默重挂下单面板
// soft/已有链时保留 selectedInstrenderStrikes 会先 park 再按 prevSelected 静默重挂下单面板
renderStrikes();
void watchCurrentExpiryQuotes();
startQuoteLiveStream();
} catch (e) {
if (seq !== chainLoadSeq || soft) return;
if (hadChain) return;
setExpirySelectStatus("选择到期日");
const tbody = document.getElementById("opt-strike-tbody");
if (tbody) {
@@ -1549,7 +1566,10 @@
"</td></tr>";
}
} finally {
if (seq === chainLoadSeq && btn) btn.disabled = false;
if (seq === chainLoadSeq && btn) {
btn.disabled = false;
if (btn.dataset.origText) btn.textContent = btn.dataset.origText;
}
}
}