[Tutorial] Cancellable Function Usage

Cancellable Function Usage

Imagine that you have a function full of asynchronous actions:

function ZombieSpawner.SpawnAsync(model, location)
    RequestModel(model)
    while not HasModelLoaded(model) do Citizen.Wait(100) end

    RequestAnimDict('move_m@drunk@verydrunk')
    while not HasAnimSetLoaded("move_m@drunk@verydrunk") do Citizen.Wait(100) end

    --
end

There is a high chance that you will need to interrupt it if something had happened.
You can’t do it without writing high coupling code, as your method will check itself if it needs to be interrupted:

function ZombieSpawner.SpawnAsync(model, location)
    RequestModel(model)
    while not HasModelLoaded(model) do
        Citizen.Wait(100)
        if not GameMode.IsRunning() then return end
    end

    RequestAnimDict('move_m@drunk@verydrunk')
    while not HasAnimSetLoaded("move_m@drunk@verydrunk") do
        Citizen.Wait(100)
        if not GameMode.IsRunning() then return end
    end

    --
end

Let’s solve this issue by introducing CancellationToken - basically a wrapper for boolean flag:

CancellationToken = {  }
CancellationToken.__index = CancellationToken

function CancellationToken.New()
    local self = { }

    setmetatable(self, CancellationToken)
    self._wasCancelled = false

    return self
end

function CancellationToken:Cancel()
    self._wasCancelled = true
end

function CancellationToken:WasCancelled()
    return self._wasCancelled 
end

Let’s declare one more helper:

function Citizen.CancellableWait(ms, token)
    Citizen.Wait(ms)
    return not token:WasCancelled()
end

And this is how we will use it:

function ZombieSpawner.SpawnAsync(model, location, token)
    RequestModel(model)
    while not HasModelLoaded(model) do
        if not Citizen.CancellableWait(100, token) then return end
    end

    --
end

Here is a full example:

local cancellationToken = CancellationToken.New()

RegisterNetEvent('gamemode:spawnZombie')
AddEventHandler('gamemode:spawnZombie', function(model, location)
    ZombieSpawner.SpawnAsync(model, location, cancellationToken)
end)

RegisterNetEvent('gamemode:removeAllZombies')
AddEventHandler('gamemode:removeAllZombies', function()
    cancellationToken:Cancel()
    ZombieSpawner.RemoveAll()
end)

I recommend to use cancellation token for async resource loading and I/O operations only.

3 Likes