What’s up everyone!
Curious about .await() - How does it work. I did some javascript research and it kept talking about promises, and things of that nature.
What is it’s purpose?
From what I was looking at, it haults execution until whatever is inside of the parenthesis ‘()’ finishes correct?
How would this work in the scope of a native?
I have some client-server communication going on, and this would be really, really handy in preventing unnecessary execution when the server is out processing some data…
Anything to share?
Would love to hear some input!
Thanks as always!
Its relatively simple
You make a promise
local p = promise.new()
This is an unresolved promise, you can do two things with this promise, resolve it, or reject it
p:resolve()
p:reject()
You can provide these two functions values
local someRandomTable = {
someUsefulData = true,
}
p:resolve(someRandomTable)
p:reject(someRandomTable)
You have the function Citizen.Await() which lets you await the promise
-- Citizen.Await handles the co-routines and polls the await, if a the Await gets rejected it will throw an error.
local someData = Citizen.Await(p)
-- do something after promise is resolved
If you reject a promise it will error, you can do something like this to handle that
local success, data = pcall(function()
local someRandomTable = Citizen.Await(p)
return someRandomTable
end)
if not success then
print(("We failed with reason %s"):format(data))
return
end
-- we succeeded! we can use the random table from data
Or just let it error, you should only use rejects if something is out of wack anyways.
6 Likes
This is pretty cool. it was initially confusing as I didn’t understand the concept of promises, but this video (on roblox
) did a great job of abstracting out the concept of promises.
I do have a few follow up questions:
And I’m also asking this while looking at the github for promises referenced on the external libraries section of the “Scripting in Lua” reference so if you don’t feel like answering it, I’ll see if I can answer it myself after reading:
- Should I always call resolve && reject with a promise or should I call what I’m expecting the promise to do?
- Can I provide any type and amount of arguments / parameters to resolve / reject?
- What actually happens when something is resolved or rejected. Does the data in the parenthesis get passed or something?
It’s a bit more clear, but not all there…I’ll keep studying regardless. Thanks this much for the detailed explanation - it’s appreciated!
I’m not entirely sure what you mean here.
You can provide any type, but it only takes one value. Doing p:resolve(val1, val2) will just result in val2 being discarded
I edited my example code a little bit because I realized I forgot to actually use the return value of Citizen.Await which returns the value of p:resolve()
You can see Citizen.Await here in the scheduler.
2 Likes
maybe I’m not as familiar with the concept of resolving and rejecting as I thought. I guess my question was:
Are resolve and reject required? Also, what determines if the code executes into a resolve or reject?
It’s still a bit confusing tbh.
Does the argument to resolve / reject have to be what we’re expecting to happen if the promise rejects / resolves? Getting close, but not 100% yet…
You should always resolve or reject a promise so that any code thats blocking on the promise will not block.
You can get a general understanding of promises from MDN, the promise implementation used by Cfx works in (relatively) the same way (Cfx’s implementation doesn’t have a then it has next, you can handle failed promises in next too)
CFX_CORE = {}
local function init_has_player_loaded()
CFX_CORE.player_data = nil
-- it should be noted that we can only use promises within valid coroutines
-- such as `CreateThread`
local p = promise.new()
CFX_CORE.has_player_loaded = p
-- we only care about setting the player data
p:next(function(plyData)
CFX_CORE.player_data = plyData
end, function (err)
-- handle error
end)
end
-- we want to create all of our data on the initial resource tick,
-- `CreateThread` will execute after the first tick.
CreateThreadNow(init_has_player_loaded)
-- reset our player loaded data so we'll await again on functions requiring a
-- valid `player_data` object
RegisterNetEvent("CFX_CORE:player_unloaded", init_has_player_loaded)
RegisterNetEvent("CFX_CORE:player_loaded", function(plyData)
CFX_CORE.has_player_loaded:resolve(plyData)
end)
CreateThread(function()
while true do
-- wait for player_data to be initialized
local player_data = Citizen.Await(CFX_CORE.has_player_loaded)
-- do something that relies on the player having loaded
Wait(0)
end
end)
1 Like
This helps tremendously. The only thing that’s still a bit fuzzy for me is the concept of the anonymous function used here:
I understand that
p:next(function(plyData)
implies that this is an anonymous function that will run on resolution of the promise taking plyData as an argument → Then running
CFX_CORE.player_data = plyData
to update CFX_CORE.player_data with said argument. (And vice versa with the anonymous function handling err)
I guess the only thing that’s confusing to me is how those arguments (both plyData and err get populated).
Are they populated when the function
init_has_player_loaded()
gets run with at least ONE argument? I saw a similar example within the Zserge promises doc where a (seemingly arbitrary) argument gets pulled out of thin air called
results
Here’s the code (and my explanation so you know I’m actually doing work to try to understand it!)
deferred.all({
--This argument is a single table containing 3-get-requests to some
-- site to pull some data; The "Deferred all" means that ALL of
-- the actions within this table must be completed in order to be
-- considered "resolved!"
http.get('http://example.com/first'),
http.get('http://example.com/second'),
http.get('http://example.com/third'),
}):next(function(results)
-- This is where the confusion happens - Is results a return from get?
-- What's supposed to go into the arguments section of the
-- "resolution" function?
end, function(results)
-- Same here - They don't do a good job of establishing context
-- which is where my confusion comes from in terms of how the
-- anonymous functions are supposed to be used...
end)
No they get populated with
RegisterNetEvent("CFX_CORE:player_loaded", function(plyData)
CFX_CORE.has_player_loaded:resolve(plyData)
end)
The results here come from when all of the promises resolve.
1 Like
Goootcha! Okay, so in this case, that event being called:
RegisterNetEvent("CFX_CORE:player_loaded", function(plyData)
CFX_CORE.has_player_loaded:resolve(plyData)
end)
Is what populates everything.
This tells me it’s the event triggering the promise resolve attempt. Is it also true that we could replace the above event’s usage of plyData with anything like dataPlyr and it’ll “translate” back to the same promise to the same “function-internal” plyData argument in the promise itself?
This is extremely helpful man, currently reading the mozilla docs to solidify everything, but it really helps translating it into our FiveM world as there aren’t many examples and usecases…
Just answered my own question: AKA - YES!
So I just conducted a test here and here are my findings:
(Based HEAAAVILY off of your example)
function fadetest()
frst_promise = promise.new()
fadestatus = frst_promise
frst_promise:next(function(string)
print("Promise has been resolved! Fade that damned screen in!")
print(string)
DoScreenFadeIn(1000)
end, function(string)
print("Dad's still out with the milk - Promise Failed, STILL let's fade the screen in!")
print(string .. " of failure")
DoScreenFadeIn(1000)
end)
end
-- Gonna Call this the "Promise-Starter"
fadetest()
--[[ Gonna Call this one the "Promise-Tester"]]
CreateThread(function()
fadestatus:resolve("balls")
end)
--And this one is the "Promise-Awaiter"
CreateThread(function()
local faderesults = Citizen.Await(fadestatus)
print("promise is resolved - go live your life!")
print(faderesults)
--DoScreenFadedIn(1000)
Wait(0)
end)
So when I ran this WITHOUT the “promise starter” and just made a direct call to
fadestatus:resolve("balls")
I got an unrecognized global variable-related error. This is because while we created the function, the promise itself has yet to exist. It has yet to be initialized. This is why it’s important to have your “Promise-Starter” (where you call the enclosing function containing the promise).
Once that was called, I could make an attempt to resolve the promise using the “Promise-Tester”
And as a result, the string “balls” gets passed into the argument “string” and gets used there AND as a return from the variable ‘faderesults’
As a final example, in the behavior testing, in the “Promise-Awaiter” thread, if that’s on a while-true-do loop, what happens is as follows:
- While promise is unresolved, Nothing
- When promise gets resolved, it runs the code underneath in an infinite loop
- When the promise gets rejected, the variable “FadeResults” gets thrown as an error
So since I answered my side question I will be marking your answer as a solution, but I just had ONNNE more question regarding rejected promises:
Will I always have to be the one who calls :reject or :resolve?
My initial expectation was that anything between the init_has_player_loaded() |aka| fadetest()
functions and the p:next section was the mechanism that dictated if a promise got resolved or rejected, and not a need for a manual calling…
Is that just not true in this case?
Once again, thanks for your wisdom and assistance and resources. For every idiot like me brave enough to ask stupid questions, there are 1000 lurkers looking to learn yet too afraid to ask, so on behalf of the lurkers who will have been educated, THANK YOU!
I come back to this post every time I forget how promises work 
2 Likes