This example uses
near-sdk 5.29.1. The contract can wait for 200 blocks, after which the yielded function executes with a timeout error if no response has arrived.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.lib.rs
Creating a Yielded Promise
In the example above, we are creating aPromise to call the contract’s function return_external_response.
Promise::new_yield returns both the yielded Promise and a YieldId that identifies it. The callback name, arguments, and gas allocation are passed when creating the promise.
Storing the Yielded Promise ID
We store theyield_id and the user’s prompt so the external service can query them using list_requests. A YieldId is serialized as a base64 string in JSON; the service passes that value unchanged when responding.
Returning the Promise
Finally, we return thePromise from request. The callback will not execute immediately: it waits until the external service provides a response or the promise times out.
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 wait 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
TheYieldId::resume method allows us to signal which yielded promise should execute and pass a response to the callback.
lib.rs
respond, passing the yield_id from list_requests and the response to the prompt. The response is serialized as JSON bytes so the callback can deserialize it as a String.
YieldId::resume returns an error if the yielded promise is no longer available. In this example, expect makes respond fail in that case, such as when a response arrives after the timeout callback has completed.
The Function that Resumes
The callback receives the arguments passed when creating the yielded promise and the result of waiting for the external response.lib.rs
return_external_response receives parameters:
- A
request_id- passed on creation - which is used to remove the request from the state - A
response- obtained through#[callback_result]- which isOk(answer)when the external service signals the resume, orErr(PromiseError::Failed)if the promise times out while waiting
In this example, we return
Response::Answer when there is a response and Response::TimeOutError when the callback receives an error.Both paths remove the stored request. Panicking in the callback would revert that removal, so we return a value to preserve the cleanup.Managing State
When using yield and resume, it’s important that you carefully manage the contract’s state. The function that creates the yielded promise finishes before the callback executes, and its state changes are already committed. 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 clean up the pending 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 changes made in request have already been committed. You should gracefully complete the callback and clean up the pending state.
Read asynchronous-call security to avoid common pitfalls when dealing with callbacks and pending state.
Testing Yield and Resume
The example’s integration tests build the contract from source and usenear-sandbox with near-api to verify both an external response and a timeout. The timeout test fast-forwards the local node. Both tests check that pending requests are removed and that completed requests cannot be resumed again.
From the repository’s contract directory, run: