【Godot】Understanding await and Coroutines in Godot Engine: Async Processing Fundamentals (Including yield Migration)

Created: 2025-12-08Last updated: 2025-12-16

Learn how to use the await keyword and coroutines in Godot 4's GDScript, from basic signal waiting to practical UI control and performance considerations. Includes migration guide from Godot 3's yield.

In game development, "waiting" and "pausing" processes occur frequently. For example, waiting for an animation to finish, waiting for a network response, or resuming processing after a certain time. Async processing and coroutines are essential concepts for handling these efficiently without stopping the main loop (frame processing).

In Godot Engine's GDScript, the await keyword was introduced to handle async processing simply yet powerfully. This article covers everything from basic await usage to the underlying coroutine mechanics, performance considerations, and migration from yield used in Godot 3.


Why Async Processing and await Matter

Games need to run smoothly at all times. If some process (like loading a huge file or complex calculations) occupies the main thread for too long, the game freezes and user experience is severely damaged. This is called blocking.

Async processing with await is a powerful weapon for avoiding this blocking. await pauses function execution at that point and returns control to the Godot engine until waiting completes. Meanwhile, the engine can continue other processes (rendering, input handling, physics calculations, etc.), so the game doesn't freeze. When the awaited event (like a signal emission) occurs, the engine automatically resumes function execution from where it stopped.

Functions with this "pause and resume" mechanism are called coroutines. In GDScript, the moment you use await inside a function, that function automatically becomes a coroutine.


Basic Usage of await

await is used to wait for signals or the completion of other coroutines.

1. Waiting for Signals

The most common usage is waiting for signals emitted from nodes.

# Example: Apply damage after attack animation finishes
func _on_attack_button_pressed():
    $AnimationPlayer.play("attack")
    # Wait for AnimationPlayer's `animation_finished` signal
    await $AnimationPlayer.animation_finished

    # Animation finished, now execute damage calculation
    print("Attack animation finished! Damage check!")

2. Waiting for a Duration (Timer)

To pause processing for a specified time, combine with the timer creation feature provided by SceneTree.

# Example: Spawn enemies after 3 seconds
func spawn_enemies_after_delay():
    print("Game started! Enemies will appear in 3 seconds.")
    # Create a SceneTreeTimer and wait for its `timeout` signal
    await get_tree().create_timer(3.0).timeout

    print("Time's up! Spawning enemies.")
    # Enemy instantiation logic here

3. Waiting for Other Coroutines to Complete

await can also wait for other functions that are themselves coroutines.

# Function that loads resources asynchronously and waits for completion
func load_level_async() -> void:
    print("Starting level data load...")
    await get_tree().create_timer(2.0).timeout # Dummy load time
    print("Level data load complete.")

# Game start sequence
func start_game():
    # First fade in the UI (coroutine)
    await fade_in_ui()

    # Then load level data asynchronously (coroutine)
    await load_level_async()

    # All preparations complete, enable player control
    print("Game Start!")

func fade_in_ui():
    var tween = create_tween()
    tween.tween_property($UI/CanvasLayer, "modulate:a", 1.0, 1.0)
    await tween.finished

Note about Tween and await: In cases where the Tween completes instantly—such as when duration is 0 or the target value is already reached at start—the finished signal may not be emitted. In such cases, await waits forever, so consider adding condition checks beforehand or implementing timeout handling.


The Relationship Between await and Coroutines

Functions containing await are automatically treated as coroutines. A coroutine is a function that can pause execution and resume later.

Coroutine Characteristics:

CharacteristicDescription
CooperativeProcessing switches are made explicitly by the programmer using the await keyword.
Pause and ResumePauses at await points and automatically resumes when the awaited target completes.
Stack PreservationEven when paused, the execution context (stack), including local variables, is preserved.

Migrating from Godot 3's yield to await

From Godot Engine 4.0 onwards, the method for writing async processing changed significantly. The yield keyword used in Godot 3 was deprecated and replaced with the await keyword.

Basic Migration Patterns

Godot 3.x (yield)Godot 4.x (await)Notes
yield(object, "signal_name")await object.signal_nameSignal waiting is the simplest migration pattern.
yield(get_tree().create_timer(time), "timeout")await get_tree().create_timer(time).timeoutTimer waiting is also written as signal waiting.
yield(func_call(), "completed")await func_call()When waiting for async function completion.

Practical Migration Example

# Godot 3.x (yield)
func wait_and_do_something():
    yield(get_tree().create_timer(2.0), "timeout")
    print("2 seconds passed!")

# Godot 4.x (await)
func wait_and_do_something():
    await get_tree().create_timer(2.0).timeout
    print("2 seconds passed!")

Common Mistakes and Best Practices

Common MistakeBest Practice
Using await every frame inside _processawait inside _process pauses processing for one or more frames, causing unintended delays. Use await for one-time sequence processing triggered by specific events, and handle per-frame checks with state variables (State Machine) instead.
await after node deletionIf a node being awaited is deleted with queue_free() during waiting, processing won't resume and causes errors. Check node existence with is_instance_valid(node) before waiting, or also await the node's tree_exiting signal.
Confusing signal connection with awaitawait is for one-time waiting. To do something every time a button is pressed, connect the button_down signal to call a corresponding function.
Ignoring return valuesSome signals pass arguments. await returns signal arguments, so use var result = await object.signal_name.

Performance Considerations and Alternative Patterns

await Overhead

Creating and managing coroutines with await has a small cost. When hundreds or thousands of objects run coroutines simultaneously, scheduling overhead accumulates and can affect performance.

Alternative Pattern: Thread Class

For truly heavy processing (complex AI calculations, large-scale data processing, file I/O), await may cause slight stuttering on the main thread. For such CPU-intensive tasks, it's best to use the Thread class to run them on a background thread.

await (Coroutine)Thread (Thread)
PurposeCooperative multitasking on main thread (waiting)Parallel processing in background (heavy computation)
ThreadMain thread onlySeparate from main thread
ImplementationJust use await keywordCreate Thread object and call function
CautionProcessing itself runs on main threadAccessing main thread data requires care (may need Mutex)

Alternative Pattern: Direct Signal Connection

For persistent responses to events, connecting signals directly with connect is the standard approach, not await.

# Connecting signals (recommended event handling)
func _ready():
    $MyButton.button_down.connect(_on_my_button_pressed)

func _on_my_button_pressed():
    print("Button was pressed!")

Summary

The await keyword in Godot Engine is a powerful tool for handling async processing and coroutines in GDScript.

ConceptKeywordRole
Async ProcessingawaitEnables waiting and resuming without blocking the main thread.
CoroutineFunction containing awaitA function that can pause and later resume execution. Enables concise complex sequences.

When migrating from Godot 3 to Godot 4, replacing yield with await is required, but await enables more intuitive and modern async processing syntax.