Skip to main content
NEAR smart contracts can yield execution, until an external service resumes them. In practice, the contract yields a cross-contract call to itself, until an external service executes a function and the contract decides to resume. This allows contracts to wait for external events, such as a response from an oracle, before continuing execution.
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 a Promise 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 the yield_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 the Promise from request. The callback will not execute immediately: it waits until the external service provides a response or the promise times out.
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

The YieldId::resume method allows us to signal which yielded promise should execute and pass a response to the callback.
lib.rs
In the example above, an external service calls 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.
Gatekeeping the ResumeSince the function used to signal the resume is public, developers must make sure to guard it properly to avoid unwanted calls. This can be done by simply checking the caller of the function

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
In the example above, the return_external_response receives parameters:
  1. A request_id - passed on creation - which is used to remove the request from the state
  2. A response - obtained through #[callback_result] - which is Ok(answer) when the external service signals the resume, or Err(PromiseError::Failed) if the promise times out while waiting
There’s plenty of timeThe contract can wait for 200 blocks before timing out.
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 (the request 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 use near-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: