How to Fix: How to disable async in component?
If your component build is unexpectedly generating async host bindings or behaving as if calls must be awaited, the problem is usually not that async was “enabled” by mistake. It happens because the component model tooling and selected bindings generator infer async behavior from the target runtime, adapter, or generated interface layer. In practice, disabling async means choosing a synchronous ABI path, generating bindings for a sync-capable target, and avoiding preview features or runtimes that require async lifting/lowering.
Table of Contents
Given a WIT world like this:
package component:adder;
/// An example world for the component to target.
world example {
export add: func(x: s32, y: s32) -> s32;
}
the exported function itself is fully synchronous. So if the generated component or host integration still appears async, the issue is almost always in the toolchain layer, not in the WIT definition.
Understanding the Root Cause
In the WebAssembly component model, a function like add is sync by definition unless your binding generator or runtime wraps it in async-friendly glue. That wrapping can happen for a few reasons:
- The host runtime only exposes component calls through an async API.
- The generated bindings are built for a mode that assumes async lowering/lifting.
- You are using an adapter, reactor model, or framework integration that normalizes all component entry points into async calls.
- The specific language bindings generator does not currently support a fully sync host path for that target.
That is why simply changing the WIT file usually does not disable async. Your WIT already describes a sync function. The real fix is to ensure that:
- The component is compiled without features requiring async execution.
- The bindings are generated for a sync-compatible target.
- The host calls the export through a synchronous API if the runtime supports one.
In other words, async is often imposed by the runtime boundary, not the function signature.
Step-by-Step Solution
The safest way to disable async behavior is to verify each layer separately.
1. Keep the WIT world synchronous
Your WIT is already correct:
package component:adder;
world example {
export add: func(x: s32, y: s32) -> s32;
}
There is nothing to remove here. No futures, no streams, and no async resource semantics are present.
2. Compile the core WebAssembly module as a plain synchronous export
Make sure the underlying module export is a normal function and does not rely on async runtimes, task executors, or callback-driven host support.
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
If you are generating this from Rust, keep the exported function simple:
#[no_mangle]
pub extern "C" fn add(x: i32, y: i32) -> i32 {
x + y
}
3. Generate the component without async-oriented host assumptions
If you are using a component tool such as wasm-tools, cargo-component, or a language-specific bindings generator, inspect whether it provides:
- a sync vs async bindings option
- a runtime flavor selection
- preview feature flags that change call semantics
For example, if your generator exposes separate sync and async APIs, choose the sync version. If it always emits async bindings for a host language, then async cannot be disabled at the generated API level for that target.
A generic component build flow looks like this:
# compile core wasm
cargo build --target wasm32-unknown-unknown --release
# create/embed component metadata
wit-component embed target/wasm32-unknown-unknown/release/adder.wasm \
--wit wit \
-o adder.embedded.wasm
# turn it into a component
wasm-tools component new adder.embedded.wasm -o adder.component.wasm
If your toolchain has an async-related flag enabled, remove it or switch to the sync mode documented by that tool.
4. Use a synchronous host API when instantiating the component
This is the step most people miss. Some runtimes expose both sync and async stores, linkers, or instance calls. If you instantiate the component using an async host context, all calls may become async even when the guest export is not.
Pseudocode for a sync host flow:
let engine = Engine::default();
let component = Component::from_file(&engine, "adder.component.wasm")?;
let mut store = Store::new(&engine, HostState::default());
let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component)?;
let add = instance.get_typed_func::<(i32, i32), i32>(&mut store, "add")?;
let result = add.call(&mut store, (2, 3))?;
If your current code uses methods like instantiate_async, call_async, or an async-enabled store configuration, switch to their sync equivalents where supported.
5. Disable async support in the runtime configuration if applicable
Some runtimes require an explicit configuration choice. If async support is enabled globally, component APIs may be routed through that path.
let mut config = Config::new();
config.async_support(false);
let engine = Engine::new(&config)?;
This exact API depends on the runtime, but the pattern is consistent: engine/store config can force async semantics.
6. Regenerate bindings after changing runtime or WIT settings
Generated component bindings often cache assumptions about imports, exports, and call style. After changing configuration, regenerate everything from scratch:
rm -rf bindings/ target/
cargo build --target wasm32-unknown-unknown --release
wasm-tools component new adder.embedded.wasm -o adder.component.wasm
If stale generated code remains, it may still expose async wrappers even after the component itself is synchronous.
7. Validate the component interface
Inspect the final component to confirm the exported function is still plain sync:
wasm-tools component wit adder.component.wasm
You should see an export equivalent to:
world root {
export add: func(x: s32, y: s32) -> s32
}
If the interface is still sync but your host API is async, the remaining issue is definitely in the host runtime layer.
Common Edge Cases
- Bindings generator always emits async APIs: Some host-language generators intentionally standardize everything as async. In that case, you cannot disable async without switching generator, target language, or runtime integration.
- Imported functions force async instantiation: Even if your export is sync, imported host functions may require async support, especially if they involve I/O, resources, or streaming abstractions.
- Mixed runtime configuration: Using a sync component with an async-enabled store or linker can still surface async methods in the host code.
- Old generated artifacts: Stale bindings, cached adapters, or previously generated component wrappers can make it look like your changes had no effect.
- Preview feature mismatch: Different versions of component-model tooling may expose slightly different behavior for the same WIT definition. Keep your toolchain versions aligned.
- Framework-level wrapping: Some embedding frameworks wrap all plugin calls in promises/futures even when the underlying WebAssembly call is synchronous.
FAQ
Can I disable async just by changing the WIT file?
No. Your WIT export is already synchronous. If calls still appear async, the cause is typically the bindings generator or runtime API, not the interface definition.
Why is my simple add function still exposed as async in the host?
Because many host runtimes choose one uniform invocation model. If the runtime or generated bindings use async for all component calls, even a trivial math function will be wrapped that way.
How do I know whether the problem is in the component or the host?
Inspect the final component interface with a tool like wasm-tools. If the export is still func(x: s32, y: s32) -> s32, the component is synchronous and the async behavior is being introduced by the host-side integration.
The practical fix is simple: keep the WIT sync, build a plain component, disable async runtime support where possible, and use sync host instantiation/call APIs. If your selected bindings generator does not support sync calls, then async is not currently disableable in that environment, and the only real workaround is changing the tool or runtime.