# Understanding sendRequestAsync

**URL:** <https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705>\
**Category:** Maker\
**Tags:** ignition81, perspective, scripting\
**Created:** [June 24, 2025, 8:04am UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705 "2025-06-24T08:04:17Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![PaulLin](https://avatars.discourse-cdn.com/v4/letter/p/c2a13f/32.png) [@PaulLin](https://forum.inductiveautomation.com/u/PaulLin)\
**Post date:** [June 24, 2025, 8:04am UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/1 "2025-06-24T08:04:17Z")

</div>

Hello there!  
I was testing the system.util.sendRequestAsync on the perspective view.  
I have a simple task: the user clicks the "Refresh" button, and it sends a request to the gateway to execute a heavy task and update the database in the end. After that, I need to update the table component by refreshing the binding. Sounds easy, and I think many users get the same task.  
I want to make it based in sendRequestAsync message and use onSuccess to update the bindings.

I wrote a simple script just to try sendRequestAsync:  
Button script

```python
def runAction(self, event):
	project_name = 'test-project'
	
	# Define the payload
	payload = {
	    "A": 10
	}
	
	# Perspective Button onClick Script
	def successFunc(response):
	    # Handle async success
	    system.perspective.print("Result =: %s" % str(response))
	    system.perspective.openPopup('ok', 'Popup/oKmessage')
	
	def errorFunc(error):
	    # Handle async error
	    system.perspective.print("Error =: %s" % str(error))
	    system.perspective.openPopup('notOK', 'Popup/errorMessage')
	
	# Asynchronous request
	system.util.sendRequestAsync(
	    project=project_name,
	    messageHandler="test_handler",
	    payload=payload,
	    onSuccess=successFunc,
	    onError=errorFunc,
	    scope="G",
	    timeoutSec=5
	)

```

"test\_handler" gateway message handler:

```python
def handleMessage(payload):
	logger = system.util.getLogger("TestHandler")
	try:
	    inputValue = payload.get("A")
	    if inputValue == 10: # Simulate success
	        return {"result": "Success, received A = %s" % inputValue}
	    else: # Simulate error
	        raise Exception("Invalid input value")
	except Exception as e:
	    logger.error("Error in handler: %s" % str(e))
	    raise

```

I got an error "_No perspective session attached to this thread_".

Error dispatching sendRequest onSuccess function:

```python
org.python.core.PyException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: No perspective session attached to this thread.

at org.python.core.Py.JavaError(Py.java:545)

at org.python.core.Py.JavaError(Py.java:536)

at org.python.core.PyReflectedFunction. __call__ (PyReflectedFunction.java:192)

at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction. __call__ (ScriptManager.java:553)

at org.python.core.PyObject. __call__ (PyObject.java:461)

at org.python.core.PyObject. __call__ (PyObject.java:465)

at org.python.pycode._pyx45.successFunc$2(:13)

at org.python.pycode._pyx45.call_function()

at org.python.core.PyTableCode.call(PyTableCode.java:173)

at org.python.core.PyBaseCode.call(PyBaseCode.java:134)

at org.python.core.PyFunction. __call__ (PyFunction.java:416)

at org.python.core.PyFunction. __call__ (PyFunction.java:411)

at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities$RequestImpl.dispatchFunc(SystemUtilities.java:1210)

at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities$RequestImpl.finishSuccessfully(SystemUtilities.java:1200)

at com.inductiveautomation.ignition.gateway.script.GatewaySystemUtilities$SendRequestHandle.successResult(GatewaySystemUtilities.java:513)

at com.inductiveautomation.ignition.common.script.message.MessageHandlerRunnable.run(MessageHandlerRunnable.java:150)

at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)

at java.base/java.util.concurrent.FutureTask.run(Unknown Source)

at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at java.base/java.lang.Thread.run(Unknown Source)

Caused by: java.lang.IllegalArgumentException: No perspective session attached to this thread.

at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.getSession(AbstractScriptingFunctions.java:104)

at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnSession(AbstractScriptingFunctions.java:118)

at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnPage(AbstractScriptingFunctions.java:47)

at com.inductiveautomation.perspective.gateway.script.PerspectiveScriptingFunctions.print(PerspectiveScriptingFunctions.java:704)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.base/java.lang.reflect.Method.invoke(Unknown Source)

at org.python.core.PyReflectedFunction. __call__ (PyReflectedFunction.java:190)

```

Help me to understand what I missed and better understand the call back function onSuccess .  
Maybe someone will advise me on a simple way to execute the same task.  
I am running Version: 8.1.47 Maker Edition  
Thank you!

---

<div class="post-metadata">

**Author:** ![royd.van.doesburg](https://avatars.discourse-cdn.com/v4/letter/r/d07c76/32.png) [@royd.van.doesburg](https://forum.inductiveautomation.com/u/royd.van.doesburg)\
**Post date:** [June 24, 2025, 8:23am UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/2 "2025-06-24T08:23:58Z")

</div>

Hello Paul,  
If your refresh-button is on the same view as the table. Couldn't u just directly refresh the data-property of the table through a button event?  
So add a `onActionPerformed`-event to the button with the following code `self.parent.parent.getChild("TableName").refreshBinding("props.data")`. Adapt the `self.parent.parent.getChild`-part based on the structure within your view.

If u insist on using MessageHandlers, u would need te create a new MessageHandler at the `perspective session event`-level. Which would then send the message to the perspective view.

I am not familiar with the `sendRequestAsync`-method. So if this is not of any help, maybe someone else can ellaborate the use of this function further.

---

<div class="post-metadata">

**Author:** ![David\_Stone](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/david_stone/32/79749_2.png) [@David\_Stone](https://forum.inductiveautomation.com/u/David_Stone)\
**Post date:** [June 24, 2025, 8:29am UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/3 "2025-06-24T08:29:46Z")

</div>

An async function runs in a different thread to the calling thread, so it does not have the ability to call perspective functions like `system.perspective.*` you have to use a message sent to a message handler on the perspective sessions and pipe your success message to it, in order for it to be able to open a popup.

---

<div class="post-metadata">

**Author:** ![PaulLin](https://avatars.discourse-cdn.com/v4/letter/p/c2a13f/32.png) [@PaulLin](https://forum.inductiveautomation.com/u/PaulLin)\
**Post date:** [June 24, 2025, 8:56am UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/4 "2025-06-24T08:56:18Z")

</div>

Hi Royd,  
Sometimes, the gateway message handler will take a longer time to update the table because it executes an RPC call and updates the table. If I put `refreshBinding("props.data")` directly after the `sentmessage`, it will get the old SQL table result.

---

<div class="post-metadata">

**Author:** ![PaulLin](https://avatars.discourse-cdn.com/v4/letter/p/c2a13f/32.png) [@PaulLin](https://forum.inductiveautomation.com/u/PaulLin)\
**Post date:** [June 24, 2025, 9:02am UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/5 "2025-06-24T09:02:22Z")

</div>

I got the issue that `system.perspective.*` does not work.  
After playing around, I received the result I was hoping for.

```python
def runAction(self, event):
	project_name = 'Filling-machine-demo'
	
	# Define the payload
	payload = {
	    "A": 10
	}
	
	# Clean the table
	self.getSibling("Table").props.data = []
	
	# Perspective Button onClick Script
	def successFunc(response):
	    logger = system.util.getLogger("AsyncSuccess")
	    logger.info("Success callback triggered with response: %s" % str(response))
	    
	    self.getSibling("status").props.text = "Updated!"
	    #Refresh the binding
	    self.getSibling("Table").refreshBinding("props.data")
	    

	def errorFunc(error):
	    logger = system.util.getLogger("AsyncError")
	    logger.error("Error callback triggered: %s" % str(error))
	    
	    self.getSibling("status").props.text = "Error!"
# self.getSibling("Table").props.data = ["Sorry, something is broken :("]
	
	# Asynchronous request
	logger = system.util.getLogger("SendRequestAsync")
	logger.info("Sending async request")
	myHandle = system.util.sendRequestAsync(
	    project=project_name,
	    messageHandler="test_handler",
	    payload=payload,
	    timeoutSec=2,
	    onSuccess=successFunc,
	    onError=errorFunc
	)
	
	self.getSibling("status").props.text = "Refreshing....."

```

"test\_handler" message handler:

```python
def handleMessage(payload):
	import time
	
	logger = system.util.getLogger("TestHandler")
	
	try:
	    # Add a 3-second delay
	    logger.info("Starting 3-second delay")
	    time.sleep(3)
	    logger.info("Delay complete, processing payload")
	    
	    # Process the payload
	    inputValue = payload.get("A")
	    if inputValue == 10: # Simulate success
	        return {"result": "Success, received A = %s" % inputValue}
	    else: # Simulate error
	        raise Exception("Invalid input value")
	except Exception as e:
	    logger.error("Error in handler: %s" % str(e))
	    raise

```

The results from logger:

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/a/1ab7dc1571739c9af08264e3f9fce593d244eb8c.png)

I added time.sleep(3) just to simulate a late response.

Thank you all for the quick response!

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [June 24, 2025, 5:23pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/6 "2025-06-24T17:23:38Z")

</div>

Calling `sendRequest` from a Perspective session makes no sense. You already are on the gateway - _all_ Perspective scripting and expressions are running on the gateway. `sendRequest` only makes sense for Vision, where scripts are truly running locally.

That is - you're introducing extra overhead to marshal results into a serialized form for no benefit on your part. If you want to invoke a long running action in Perspective, use `system.util.invokeAsynchronous`.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [June 24, 2025, 5:31pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/7 "2025-06-24T17:31:20Z")

</div>

> [@paul-griffith](#):
>
> only makes sense for Vision

Not quite. It is helpful in gateway/Perspective scope if a message handler is defined in another project.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [June 24, 2025, 5:34pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/8 "2025-06-24T17:34:46Z")

</div>

Okay, fair enough. `invokeAsynchronous` also doesn't have the nice `onSuccess`/`onError` callbacks.

It'd be really nice if Jython could automatically call through SAM/functional interface methods, so you could use `CompletableFuture` without so much ceremony...

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [June 24, 2025, 5:37pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/9 "2025-06-24T17:37:01Z")

</div>

> [@paul-griffith](#):
>
> without so much ceremony

I've wondered if Jeff Allen's private Jython3 testbed implements `async` and `await` with `CompletableFuture`....

---

<div class="post-metadata">

**Author:** ![PaulLin](https://avatars.discourse-cdn.com/v4/letter/p/c2a13f/32.png) [@PaulLin](https://forum.inductiveautomation.com/u/PaulLin)\
**Post date:** [June 25, 2025, 12:28pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/10 "2025-06-25T12:28:39Z")

</div>

What will be your go-to implementation? My concern is primarily the delay in the execution of RPC and database insert. I can simply refresh the table binding every 5 seconds, but it is not "nice" solution.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [June 25, 2025, 3:49pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/11 "2025-06-25T15:49:30Z")

</div>

There's no RPC involved. Your script is starting on the gateway and ending on the gateway - that's a fundamental piece of the architecture of Perspective. Under the hood, we're using a websocket to shuttle all property values from the gateway/backend to your frontend session in the user agent, but you have no control over that, and nothing you're doing with arbitrary code (scripts, expressions) is running in the frontend in Perspective.

What I'm recommending is basically equivalent to what you have, you just get to define all the logic in one place (either in your script directly or, as Phil will recommend, in the project library) - and has the benefit of being able to send arbitrary data through to the callback without serialization overhead. The only thing the below example doesn't do is timeout automatically after a few seconds. If I _had_ to do that, I'd look into Java's CompletableFuture API to 'chain' my actions together; it's not quite worth it IMO for the basic example here but incredibly powerful for composing asynchronous stuff together; see [this post](https://forum.inductiveautomation.com/t/issues-when-using-system-util-invokeasynchronous-i-get-a-nonetype-object-is-not-callable-error/97022/11) for some context to get started.

```python
def runAction(self, event):	
	def successFunc(response):
		logger = system.util.getLogger("AsyncSuccess")
		logger.info("Success callback triggered with response: %s" % str(response))
		
		self.getSibling("status").props.text = "Updated!"
		self.getSibling("Table").refreshBinding("props.data")

	def errorFunc(error):
		logger = system.util.getLogger("AsyncError")
		logger.error("Error callback triggered: %s" % str(error))
		
		self.getSibling("status").props.text = "Error!"

	def callback(payload):
		import time
		
		logger = system.util.getLogger("TestHandler")
		
		try:
			# Add a 3-second delay
			logger.info("Starting 3-second delay")
			time.sleep(3)
			logger.info("Delay complete, processing payload")
			
			# Process the payload
			inputValue = payload.get("A")
			if inputValue == 10: # Simulate success
				successFunc({"result": "Success, received A = %s" % inputValue})
			else: # Simulate error
				errorFunc(Exception("Invalid input value"))
		except Exception as e:
			logger.error("Error in handler: %s" % str(e))
			errorFunc(e)

	# Define the payload
	payload = {
		"A": 10
	}
	
	# Clean the table
	self.getSibling("Table").props.data = []
	
	# Asynchronous request
	logger = system.util.getLogger("SendRequestAsync")
	logger.info("Sending async request")
	system.util.invokeAsynchronous(callback, args=[payload])
	
	self.getSibling("status").props.text = "Refreshing....."

```

---

<div class="post-metadata">

**Author:** ![WillMT10](https://avatars.discourse-cdn.com/v4/letter/w/7feea3/32.png) [@WillMT10](https://forum.inductiveautomation.com/u/WillMT10)\
**Post date:** [July 25, 2026, 5:19pm UTC](https://forum.inductiveautomation.com/t/understanding-sendrequestasync/107705/12 "2026-07-25T17:19:42Z")

</div>

I know this is an old thread, but I have a related question since queuing wasn't covered.

Even with the serialization overhead, is there value in using `sendMessage`/`sendRequest` in Perspective to hit a Dedicated thread so that I don't have to try to roll my own queuing behavior? I'm trying to prevent users from inadvertently overwhelming the gateway and database if they all trigger a heavy script at the same time. Or is there a better, standard way to queue up asynchronous tasks in Perspective that could come from many sessions?
