Fix intermittent empty options expiry dropdown after coin select.
Retry instrument fetch, reject empty chains instead of caching them, and ignore stale loadChain races. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
let lastGoodPositions = null;
|
||||
let lastGoodPositionsAt = 0;
|
||||
let positionsRefreshSeq = 0;
|
||||
let chainLoadSeq = 0;
|
||||
let refreshAllTimer = null;
|
||||
let pendingRefreshTimer = null;
|
||||
let pendingTtlSeconds = 600;
|
||||
@@ -484,8 +485,8 @@
|
||||
const sel = document.getElementById("opt-exp-select");
|
||||
if (!sel) return;
|
||||
const prev = preserveSelection !== false ? sel.value : "";
|
||||
sel.innerHTML = '<option value="">选择到期日</option>';
|
||||
const exps = (state.chain && state.chain.expiries) || [];
|
||||
sel.innerHTML = '<option value="">选择到期日</option>';
|
||||
exps.forEach(function (e) {
|
||||
const o = document.createElement("option");
|
||||
o.value = String(e.exp_time);
|
||||
@@ -497,6 +498,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setExpirySelectStatus(text) {
|
||||
const sel = document.getElementById("opt-exp-select");
|
||||
if (!sel) return;
|
||||
sel.innerHTML = "";
|
||||
const o = document.createElement("option");
|
||||
o.value = "";
|
||||
o.textContent = text || "选择到期日";
|
||||
sel.appendChild(o);
|
||||
}
|
||||
|
||||
function chainHasExpiries(chain) {
|
||||
return !!(chain && Array.isArray(chain.expiries) && chain.expiries.length > 0);
|
||||
}
|
||||
|
||||
function renderExpiries() {
|
||||
renderExpiryOptions(true);
|
||||
renderIndexLine();
|
||||
@@ -938,25 +953,93 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadChain() {
|
||||
const d = await apiJson("/api/options/chain?underlying=" + encodeURIComponent(state.underlying));
|
||||
if (!d.ok) {
|
||||
alert(d.msg || "加载失败");
|
||||
return;
|
||||
async function loadChain(opts) {
|
||||
const soft = !!(opts && opts.soft);
|
||||
const uly = state.underlying;
|
||||
const seq = ++chainLoadSeq;
|
||||
const btn = document.getElementById("opt-load-chain");
|
||||
if (btn && !soft) btn.disabled = true;
|
||||
if (!soft) {
|
||||
setExpirySelectStatus("加载到期日中…");
|
||||
const tbody = document.getElementById("opt-strike-tbody");
|
||||
if (tbody) {
|
||||
tbody.innerHTML =
|
||||
'<tr><td colspan="' + strikeTableColspan() + '" class="muted">加载期权链…</td></tr>';
|
||||
}
|
||||
}
|
||||
try {
|
||||
let d = null;
|
||||
let lastMsg = "";
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
if (seq !== chainLoadSeq) return;
|
||||
d = await apiJson("/api/options/chain?underlying=" + encodeURIComponent(uly));
|
||||
if (seq !== chainLoadSeq) return;
|
||||
if (d && d.ok && chainHasExpiries(d)) break;
|
||||
lastMsg = (d && (d.msg || d.chain_error)) || "暂无到期日";
|
||||
d = null;
|
||||
if (attempt === 0) {
|
||||
if (!soft) setExpirySelectStatus("重试加载到期日…");
|
||||
await new Promise(function (resolve) { setTimeout(resolve, 400); });
|
||||
}
|
||||
}
|
||||
if (seq !== chainLoadSeq) return;
|
||||
if (!d || !d.ok || !chainHasExpiries(d)) {
|
||||
if (chainHasExpiries(state.chain) && state.chain.underlying === uly) {
|
||||
if (!soft) {
|
||||
renderExpiries();
|
||||
renderStrikes();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (soft) return;
|
||||
setExpirySelectStatus("选择到期日");
|
||||
const tbody = document.getElementById("opt-strike-tbody");
|
||||
if (tbody) {
|
||||
tbody.innerHTML =
|
||||
'<tr><td colspan="' + strikeTableColspan() + '" class="muted">' +
|
||||
(lastMsg || "暂无到期日,请点「刷新链」") +
|
||||
"</td></tr>";
|
||||
}
|
||||
alert(lastMsg || "加载到期日失败,请点「刷新链」重试");
|
||||
return;
|
||||
}
|
||||
const keepSel = soft && state.selectedInst;
|
||||
const keepExp = soft ? (document.getElementById("opt-exp-select") || {}).value : "";
|
||||
state.chain = d;
|
||||
panelCache.chain = d;
|
||||
panelCache.underlying = uly;
|
||||
panelCache.optType = state.optType;
|
||||
if (!soft) {
|
||||
state.selectedInst = null;
|
||||
resetMoneyFilterToAll();
|
||||
state.strikeExpandAll = false;
|
||||
const expandCb = document.getElementById("opt-strike-expand-all");
|
||||
if (expandCb) expandCb.checked = false;
|
||||
parkOrderPanel();
|
||||
}
|
||||
updateUnderlyingLabel();
|
||||
renderExpiries();
|
||||
if (soft && keepExp) {
|
||||
const sel = document.getElementById("opt-exp-select");
|
||||
if (sel && Array.from(sel.options).some(function (o) { return o.value === keepExp; })) {
|
||||
sel.value = keepExp;
|
||||
}
|
||||
}
|
||||
renderStrikes();
|
||||
if (soft && keepSel) state.selectedInst = keepSel;
|
||||
} catch (e) {
|
||||
if (seq !== chainLoadSeq || soft) return;
|
||||
setExpirySelectStatus("选择到期日");
|
||||
const tbody = document.getElementById("opt-strike-tbody");
|
||||
if (tbody) {
|
||||
tbody.innerHTML =
|
||||
'<tr><td colspan="' + strikeTableColspan() + '" class="muted">加载失败: ' +
|
||||
String((e && e.message) || e) +
|
||||
"</td></tr>";
|
||||
}
|
||||
} finally {
|
||||
if (seq === chainLoadSeq && btn) btn.disabled = false;
|
||||
}
|
||||
state.chain = d;
|
||||
panelCache.chain = d;
|
||||
panelCache.underlying = state.underlying;
|
||||
panelCache.optType = state.optType;
|
||||
state.selectedInst = null;
|
||||
resetMoneyFilterToAll();
|
||||
state.strikeExpandAll = false;
|
||||
const expandCb = document.getElementById("opt-strike-expand-all");
|
||||
if (expandCb) expandCb.checked = false;
|
||||
parkOrderPanel();
|
||||
updateUnderlyingLabel();
|
||||
renderExpiries();
|
||||
renderStrikes();
|
||||
}
|
||||
|
||||
async function openPosition() {
|
||||
@@ -1756,7 +1839,7 @@
|
||||
refreshPendingOrders();
|
||||
startPendingOrdersPoll();
|
||||
const hasCache =
|
||||
panelCache.chain &&
|
||||
chainHasExpiries(panelCache.chain) &&
|
||||
panelCache.underlying === state.underlying &&
|
||||
panelCache.optType === state.optType;
|
||||
if (hasCache) {
|
||||
@@ -1764,6 +1847,8 @@
|
||||
renderExpiries();
|
||||
renderStrikes();
|
||||
refreshAllPositions();
|
||||
// 后台静默刷新,避免缓存过期后到期日变空
|
||||
loadChain({ soft: true });
|
||||
return;
|
||||
}
|
||||
requestAnimationFrame(function () {
|
||||
|
||||
Reference in New Issue
Block a user