Promises and Await

In Lemonate, many engine API functions return Promises to represent asynchronous operations. Since Lua does not natively support Promises or `await`, Lemonate provides its own implementation to support asynchronous workflows in a way that’s both expressive and engine-friendly.

This page explains what Promises and await are, how Lemonate implements them, and how you should use them when working with asynchronous engine features.

What is a Promise?

A Promise represents a value that may become available in the future. For example, loading a texture, creating a material, or constructing a mesh may return a Promise.

In Lemonate, Promises are not built into Lua itself, but are returned by many engine functions.

Because then is a reserved keyword in Lua, Lemonate uses the method :next() instead of the common .then() syntax to register callbacks.

Example:

Loader.loadTexture("/resources/textures/wood"):next(function(texture)
    -- use loaded texture, for example set it to a material or draw it using canvas API
end)

The callback inside :next() is executed once the asynchronous operation completes.

What is Await?

The function await(promise) is a utility provided by Lemonate that allows you to pause the current function until the Promise is resolved, and get its result directly.

This syntax is not built into Lua, but Lemonate simulates it by suspending the function using coroutines internally.

Example:

local texture = await(Loader.loadTexture("/resources/textures/wood"))
local material = await(Material:create())
local mesh = await(SgSphere:create({
    radius = 2,
    material = material,
    transform = Transform.new(Vector3.new(0, 5, 0))
}))

This style makes asynchronous code easier to read and write, especially when performing multiple dependent steps in sequence.

When to Use Await

Use await(promise) when:

  • You are writing code that needs a result before continuing.

  • The operation is part of a setup or loading phase where blocking is acceptable.

  • You want cleaner, linear-looking logic.

However, await will block the current function and also pause rendering of the frame. This can affect performance or responsiveness if used improperly.

When to Use :next()

Use :next(callback) when:

  • You want to avoid blocking execution or rendering.

  • You are inside a rendering update loop or time-sensitive function.

  • You want to chain multiple operations without halting the main thread.

This is the preferred style in performance-critical or real-time parts of your game.

Example:

Loader.loadTexture("/resources/textures/wood"):next(function(texture)
    Material:create():next(function(material)
        material:setTexture("albedo", texture)
    end)
end)

Chaining Promises

You can chain multiple asynchronous operations by returning Promises from within :next() callbacks. This allows a sequence of asynchronous tasks to be performed in order.

Example:

Material:create():next(function(material)
    return Loader.loadTexture("/resources/textures/wood"):next(function(texture)
        material:setTexture("albedo", texture)
        return SgSphere:create({
            radius = 2,
            material = material,
            transform = Transform.new(Vector3.new(0, 5, 0))
        })
    end)
end):next(function(mesh)
    print("Sphere mesh created at:", mesh.getTransform().position)
end)

Each step will wait for the previous one to complete. This style avoids blocking and keeps rendering smooth.

Handling Errors with :catch()

If something goes wrong in a Promise chain, you can handle it using :catch(). This method accepts a callback function that receives the error.

Example:

Loader.loadTexture("/resources/textures/missing.png")
  :next(function(texture)
      print("Texture loaded successfully")
  end)
  :catch(function(err)
      print("Failed to load texture:", err)
  end)

You can also use pcall when working with await() to safely catch errors:

local ok, texture = pcall(function()
    return await(Loader.loadTexture("/resources/textures/missing.png"))
end)

if not ok then
    print("Error loading texture:", texture)
end

Summary

  • Lemonate provides Promises to handle asynchronous engine tasks.

  • Use :next() to attach callbacks to Promises. This is non-blocking and safe during rendering.

  • Use await(promise) when you need a blocking, linear flow (e.g., during setup or loading).

  • Avoid using await in places where rendering must continue smoothly.

  • Chain multiple Promises using :next(), and handle errors using :catch() or pcall() with await().