The contract can wait for 200 blocks - around 2 minutes - after which the yielded function will execute, receiving a “timeout error” as input
Yielding a Promise
Let’s look at an example that takes a prompt from a user (e.g. “What is 2+2”), and yields the execution until an external service provides a response.Creating a Yielded Promise
In the example above, we are creating aPromise to call the contract’s function return_external_response.
Notice that we create the Promise using env::promise_yield_create, which will create an identifier for the yielded promise in the YIELD_REGISTER.
Retrieving the Yielded Promise ID
We read theYIELD_REGISTER to retrieve the ID of our yielded promise. We store the yield_id and the user’s prompt so the external service query them (the contract exposes has a function to list all requests).
Returning the Promise
Finally, we return thePromise, which will not execute immediately, but will be yielded until the external service provides a response.
What is that `self.request_id` in the code?
What is that `self.request_id` in the code?
The
self.request_id is an internal unique identifier that we use to keep track of stored requests. This way, we can delete the request once the external service provides a response (or the waiting times out)Since we only use it to simplify the process of keeping track of the requests, you can remove it if you have a different way of tracking requests (e.g. an indexer)Signaling the Resume
Theenv::promise_yield_resume function allows us to signal which yielded promise should execute, as well as which parameters to pass to the resumed function.
In the example above, the respond function would be called by an external service, passing which promise should be resume (yield_id), and the response to the prompt.
The Function that Resumes
The function being resumed will have access to all parameters passed to it, including those passed during the yield creation, or the external service response. In the example above, thereturn_external_response receives parameters:
- A
request_id- passed on creation - which is used to remove the request from the state - A
response- passed when signaling to resume - which contains the external response, orNoneif the contract timed out while waiting
Notice that, in this particular example, we choose to return a value both if there is a response or a time outThe reason to not raise an error, is because we are changing the state (removing the request in line
#7), and raising an error would revert this state changeManaging State
When using yield and resume, it’s important that you carefully manage the contract’s state. Because of its asynchronous execution, the contract function in which the contract yields and resumes are independent. If you change the state of the contract in the function where you yield the promise (therequest function here), then you need to make sure that you revert the state in the function that resumes (the return_external_response function here) in the case that the promise times out or the response is invalid.
It is best practice to check the validity of the response within the function where the resume is signaled (the respond function here) and panic if the response is not valid; the external service can attempt to respond again before the promise times out. You should not panic in return_external_response as this is only called when the promise has been resolved (it was resumed or timed out), meaning it can’t be resumed again, and the state in request has been settled. You should gracefully complete the function and revert the state.
Read asynchronous-call security to avoid common pitfalls when dealing with callbacks and pending state.