slint-ui/slint · error · std::io::Error
{e:?}
Error message
{e:?} What it means
After the `load_file` JS callback returns a Promise, the LSP awaits it as a JsFuture; if the promise resolves to a rejection (the file could not actually be read by the browser), the rejection JsValue is Debug-formatted into a std::io::Error. This is thrown because awaiting the file-load promise is mandatory to produce the source text the compiler needs. The Debug form loses the human-readable JS error message.
Solutions
- Open the browser devtools console to read the actual rejection reason hidden behind the debug-formatted JsValue.
- Ensure the JS callback rejects only on real errors and resolves with a UTF-8 string for success (stringify Blobs/ArrayBuffers before returning).
- Fix file serving: correct path mapping, MIME/permissions, and CORS so the fetch inside `load_file` succeeds.
- Add retry/backoff in the host JS `load_file` for transient network failures.
- Upgrade wasm-bindgen/wasm-bindgen-futures consistently in the LSP build if JsFuture conversion is involved in the failure.
Example fix
// before (host JS)
const load_file = (path) => fetch(path).then(r => r.arrayBuffer());
// after
const load_file = async (path) => {
const res = await fetch(path);
if (!res.ok) throw new Error(`HTTP ${res.status} for ${path}`);
return res.text();
}; Defensive patterns
Strategy: retry
Validate before calling
// JS host side: ensure the endpoint is reachable before init
const probe = await fetch(basePath + '/health');
if (!probe.ok) throw new Error('file server unavailable'); Type guard
// JS host side: only resolve with strings
function asText(v) { return typeof v === 'string' ? v : Promise.reject(new Error('load_file must resolve to a string')); } Try / catch
const load_file = async (path) => {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await fetch(path);
if (res.status >= 500 || res.status === 0) throw new Error('transient');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.text();
} catch (e) {
if (attempt === 2) throw e;
await new Promise(r => setTimeout(r, 500 * 2 ** attempt));
}
}
}; Prevention
- Resolve the promise with a UTF-8 string, never raw ArrayBuffer/Blob.
- Add retry with backoff for transient network errors in the JS callback.
- Map HTTP status codes to descriptive Error messages before rejecting.
- Verify CORS/preflight configuration for the file endpoints.
- Keep wasm-bindgen and wasm-bindgen-futures versions in sync.
When it happens
Trigger: The injected JS `load_file` function's returned Promise rejects: fetch returned non-2xx (mapped to rejection), network failure, the callback threw asynchronously, or resolved with a non-string that downstream handling turns into a failure.
Common situations: 404/CORS on the served file in the browser, WebSocket-based file service disconnecting, a Promise polyfill mismatch in older browsers, or the host page's `load_file` rejecting because the path is outside the served virtual filesystem.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of slint-ui/slint@bb937076de (2026-09-16).
Data as JSON: /api/errors/ece834d5dc366af2.
Report an issue: GitHub.
Appendix: source
Thrown at tools/lsp/wasm_main.rs:480
.handle_request(method, params, &mut ctx)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(result)
}
#[wasm_bindgen]
pub async fn reload_config(&self) -> JsResult<()> {
let mut ctx = self.ctx.lock().await;
language::load_configuration(&mut ctx).await.map_err(|e| JsError::new(&e.to_string()))
}
}
async fn load_file(path: String, load_file: &Function) -> std::io::Result<String> {
let string_promise = load_file
.call1(&JsValue::UNDEFINED, &path.into())
.map_err(|x| std::io::Error::new(ErrorKind::Other, format!("{x:?}")))?;
let string_future = wasm_bindgen_futures::JsFuture::from(js_sys::Promise::from(string_promise));
let js_value =
string_future.await.map_err(|e| std::io::Error::new(ErrorKind::Other, format!("{e:?}")))?;
return Ok(js_value.as_string().unwrap_or_default());
}
// Use a JSON friendly representation to avoid using ES maps instead of JS objects.
fn to_value<T: serde::Serialize + ?Sized>(
value: &T,
) -> std::result::Result<wasm_bindgen::JsValue, serde_wasm_bindgen::Error> {
value.serialize(&serde_wasm_bindgen::Serializer::json_compatible())
}
View on GitHub (pinned to bb937076de)