How to properly use pcall()

Curious question I’ve always had, which option is “better” or “correct” and why

local Data
local Success, Error = pcall(function()
	Data = DataStore:GetAsync("Blah")
end)

if Success then
    print(Data)
else
    warn(Error)
end
local Success, Data = pcall(function()
	return DataStore:GetAsync("Blah")
end)

if Success then
    print(Data)
else
    warn(Data)
end

Doesn’t have to be data related, just anything you’d use a pcall for

4 Likes

From what I understand: A p(rotected)call() will not error, instead it provides you with two variables: Success and Error. If pcall successfully manages to function, then it will run the code. If it errors, it returns the error message. Pcalls() are used for more security regarding tasks that might not work properlly. More and that here:

2 Likes

The first makes more sense in terms of readability, as the variable is named after the expected return value.
The second is a little neater as it doesn’t create unnecessary variables.

Most cases where I’ve used pcall() it has been in a retry loop, so the return variables are defined in a wider scope, but following the second pattern.
For example:

local success, data
local attempts = 0
repeat
    attempts += 1
    success, data = pcall(function()
        return dataStore:GetAsync("myData")
    end)
    if success then
        print(data)
    else
        warn(data)
        task.wait(3)
    end
until success or attempts == 5
2 Likes

First option, why?

I will just list the pros and cons for a better understanding

pros:

  • Readability: The variable names (Data, Success, Error) clearly indicate their purpose, which can enhance code readability.
  • Explicit Assignment: The variables are explicitly declared before the pcall block, making their scope clear.

cons:

Possibly Redundant Declarations: If the variables are only used within the pcall block, their declaration outside might seem redundant.

How to use pcall()

The pcall() function stands for “protected call.” It allows you to call a function in a protected mode, meaning it catches errors that occur during the function execution.

Returning Values:

  • If the function returns values, ensure that you capture them in the correct order.
  • Use descriptive variable names to improve code readability.

Example [datastore]

local DataStoreService = game:GetService("DataStoreService")
local DataStoreKey = "Hi"

local DataStore = DataStoreService:GetDataStore(DataStoreKey)

local success, data = pcall(function()
    return DataStore:GetAsync("Blah")
end)

if success then
    print("Data retrieved successfully:", data)
else
    warn("Error retrieving data:", data)
end

In this example, pcall() is used to protect the DataStore:GetAsync() call. If an error occurs (e.g., due to network issues), it is caught and handled in the error block.

3 Likes

Both are correct, but when it comes to saying which is better… I’ll say the second one as it’s not just cleaner but also reduces that one line of code you write to define data (see how u have an extra line on the first code you provided? It’s unnecessary with the second one!)

Second option feedback:

local Success, Data = pcall(function()
	return DataStore:GetAsync("Blah")
end)

if Success then
    print(Data)
else
    warn(Data)
end

Pros:

  • Conciseness: The code is more concise, and there are fewer lines of code.
  • Less Scope Pollution: The variables are only defined within the scope where they are needed, reducing the chance of unintentional interference with other variables.

Cons:

Implicit Assignment: The assignment is implicit within the function, so someone reading the code might need to look inside the function to understand the purpose of the variables.

Considerations:

  • Context Matters: In some situations, like retry loops, where variables need to be in a wider scope, the second option may be more appropriate.
  • Readability vs. Conciseness: Choose based on what improves the readability of your code. If the variables are essential to understanding the logic, the first option might be better. If they are just intermediate values and their names don’t add much information, the second option might be cleaner.

Ultimately, both approaches are acceptable, and the “better” option depends on your coding style, the specific use case, and your preferences or coding standards.

If I understand this right then I have a comment on this. For some functions, you’ll have to always look into the chunk no matter what in both case to understand the purpose. But it will vary on the people.

I always see people do the wrapper syntax when it really isn’t necessary

local s, r = pcall(function()
    return object:method(params1, param2)
end)

This could be simplified to

local s, r = pcall(object.method, object, params1, params2)

It also helps a bit with performance because you’re not creating a new function and also imo it looks nicer

3 Likes

I use the second option. You should only use pcall where you know that errors could happen, but may not happen. This is only the case for requests that end up outside the server. For example, datastores, MessaingService, HttpService, and MarketplaceService.

Do not use pcalls to fix errors other than those. It will only cover up the error, not fix them.

Although, here’s a case it is used for that isn’t what I mentioned:

local function hasValue(obj: Instance, i: string)
    return (pcall(function()
        return obj[i]
    end)
end

once again you can save performance by avoiding creating function wrappers…

local function indexFor(obj: Instance, s: String)
    return obj[s]
end

--use pcall directly instead of a function proxy
local cframeExists: boolean = pcall(indexFor, something, 'CFrame')

In highly complex scripts it may be necessary to cover up the error as you can’t predict all possible scenarios. For example I had a script that would error when a player was wearing a specific hat. pcall() allowed the game to continue running (rather than breaking for everyone in the server), while reporting the issue to a dev inbox.

This is not a good idea for most cases. Errors are supposed to stop things immediately, because they are errors. Letting them continue to run can cause even bigger issues, which you don’t know yet because they are errors, aka unintentional game-breaking issues.

With your specific example, it doesn’t really make much difference, but if you are transitioning to using typed Luau, the first way is a better fit IMO.

Firmly disagree. It’s a good idea in most cases as long as you understand how your script works & the consequences of potential errors.

If there’s an error in your sound system because something didn’t load on time, is that a reason to stop the entire game from running? No.

Should you use pcall() in every script? Probably not. But at least in my experience, most code isn’t critical/essential and should be allowed to safely error, create a log, and move on.

Read what i said:

I treat errors by this definition. If you clearly know what will cause an error, when it will happen, and what will happen afterwards, then you shouldn’t be calling it that. You can it an expected behavior, or even a feature. Sound doesn’t load? That’s expected because of unstable bandwidth or quirky APIs. This distinguishment is important for actual game-breaking errors, not some petty game hiccups.

Let me reword myself, you can definitely have erroneous code “reboot” and try again. What I was saying is you shouldn’t let errors continue to execute what’s supposed to happen afterwards. It should start over from the beginning. If you failed to get the player data, you should try requesting for it again. Do not let it continue as normal and let the player play as normal because it will ruin their save.

Errors have a specific meaning in Lua…

Let me reword myself, you can definitely have erroneous code “reboot” and try again.

Which is exactly what pcall() can be used to accomplish.

Lol what? Thats what error handling is for. Especially for specific use cases like critical code running under a statement that might error inconsistently. Definitely varies by case

Or just don’t save it? Better yet, ProfileService is open source.

I see people use pcalls in different ways and depending on how it’s structured that may or may not be the case. It could enclose the entire function or just a part of it. Code structure always screw you over.

And either option may be advantageous depending on the code…

1 Like