Async queries
Some GraphQL requests, such as the execution of processings or reports, may take long times to execute. These times could exceed internal GraphQL timeouts or HTTP timeouts (for instance when you’re executing a query from a browser-based IDE such as GraphiQL), in which case the status and the result of the execution will be lost, even though it would complete successfully. To avoid this problem, you can run any Business NXT GraphQL request asynchronously. This works as follows:
- You request the excution of a query ascynhronously. The service will queue the request and immediatelly return an operation identifier. This identifier will then be used to fetch the result.
- You will ask for the result of the async query using the previously returned operation identifier. If the operation is taking a long time to execute, you will have to periodically poll for the result.
- In addition, you can request the list of all your asynchronous queries, which would return their operation identifier and status.
Note
An asynchronous query carries the request of executing a synchronous query/mutation. The system executes this query normally (in a synchronous way) but hides the details from the caller and then makes its result available on a further request upon completion.
Tip
There are several important thing to note:
- You can only execute a maximum of 10 asynchronous query at a time and you can only start a new one 5 seconds after the previous one has been started. (Note: These numbers may be subject to change.)
- You cannot execute an asynchronous query within an asynchronous query.
- The result of the asynchronous query is the stringified JSON of the synchronous query.
Warning
Aynchronous queries cannot contain join operations (joinup_
and joindown_
fields) or an @export
directive.
If you try to execute an asynchronous query containing any of these, you will not get the expected results. This is because an async query containing joins or the @export
directive implies a sequence of requests to the backend: a first one to retrieve a set of data, and then another one after, based on the previously retrieved data. However, in the case of async queries, the first batch of requests are sent to the backend, but the response is not awaited for. Instead, the API returns an operation ID that is later used to query for the result. Only when you ask for the result of the async query, the response from the backend is read and interpreted. Therefore, the expected sequence of requests to the backend cannot be executed in the case of async queries.
Note
The result of an asynchronous request is stored on the server for 96 hours. During this time, you can read the result multiple times. After this period, the result is deleted and will no longer be available for reading.
Making an asynchronous request
An asynchronous execution is requested with a mutation operation using the asyncQuery
field.
The asyncQuery
field has two arguments:
Field | Type | Description |
---|---|---|
query |
String |
Contains the GraphQL query to be executed ascynhronously (it can either be a query or a mutation). |
args |
Dictionary |
Contains the key-value pairs representing the arguments for the query. |
The result of executing an asynchronous query contains the following fields:
Field | Type | Description |
---|---|---|
operationId |
String |
The identifier of the requested operation. |
status |
TaskStatus |
An enumeration with several possible values: ERROR : the operation execution finished because of an errorSUCCESS : the request has completed successfullyQUEUED : the request has been received and placed in a queue but the execution has not started |
Here is an example that requests the first five general ledger accounts in an asynchronous way:
mutation
{
asyncQuery(query:"""
query {
useCompany(no: 123456)
{
generalLedgerAccount(first: 5) {
totalCount
items {
accountNo
name
}
}
}
}
""")
{
operationId
status
}
}
{
"data": {
"asyncQuery": {
"operationId": "29bb4abe-5299-4542-b1e4-c05aca11c3ed",
"status": "QUEUED"
}
},
"extensions": {
"vbnxt-trace-id": "43c281879ec08b257563ddbb5668a12b"
}
}
Typically, you would need to pass arguments to the query that needs to be executed asynchronously. This is done using the args
argument of the asyncQuery
field. The next example shows the previous query modified to contain arguments for the company number and the page size:
mutation ($args: Dictionary)
{
asyncQuery(query:"""
query read($cid : Int!, $pagesize : Int!){
useCompany(no: $cid)
{
generalLedgerAccount(first: $pagesize) {
totalCount
items {
accountNo
name
}
}
}
}
""", args: $args)
{
operationId
status
}
}
{
"args" : {
"cid" : 123456,
"pagesize" : 5
}
}
The returned operationId
must be used to read the result with another request.
Reading the result of an asynchronous request
Reading the result of an asynchronous request is done with the asyncResult
field of the Query type. This field has a single argument, operationId
, which is a string containing the identifier returned by an asyncQuery
request.
query fetch_results($oid : String!)
{
asyncResult(id: $oid)
{
operationId
status
error
data
createdAt
completedAt
}
}
{
"data": {
"asyncResult": {
"operationId": "caa8812d-fdb2-4546-86a9-eab30e9dde20",
"status": "SUCCESS",
"error": null,
"data": "{\"data\":{\"useCompany\":{\"generalLedgerAccount\":{\"totalCount\":342,\"items\":[{\"accountNo\":1000,\"name\":\"Forskning og utvikling\"},{\"accountNo\":1020,\"name\":\"Konsesjoner\"},{\"accountNo\":1030,\"name\":\"Patenter\"},{\"accountNo\":1040,\"name\":\"Lisenser\"},{\"accountNo\":1050,\"name\":\"Varemerker\"}]}}}}",
"createdAt": "2022-09-23T12:34:07Z",
"completedAt": "2022-09-23T12:34:11Z"
}
},
"extensions": {
"vbnxt-trace-id": "ad02666f0531856aaadfc74625dc37f1"
}
}
The result is available as a string containing the JSON result of the requested query. You would deserialize this just as you’d do if the request was executed synchronously.
However, data may not be available immediatelly. If the operation is long-runnning and not yet completed, the returned status would be QUEUED
, as shown next:
{
"data": {
"asyncResult": {
"operationId": "caa8812d-fdb2-4546-86a9-eab30e9dde20",
"status": "QUEUED",
"error": null,
"data": "null",
"createdAt": "2022-09-23T12:34:07Z",
"completedAt": null
}
},
"extensions": {
"vbnxt-trace-id": "eb52491bfe7268e89d61a1121c0453af"
}
}
In this case, you have to poll periodically for the result until the status changes to SUCCESS
or ERROR
.
Getting the list of asynchronous request
The result of the requests that executed asynchronously is available for 96 hours on the server. During this time, you can read it multiple times.
If you want to know what asynchronous operations you have requested and are still available on the server, you can do so with a query using the asyncRequests
field. What you get back is the operation identifier and the status of each operation:
query
{
asyncRequests
{
operationId
status
}
}
{
"data": {
"asyncRequests": [
{
"operationId": "caa8812d-fdb2-4546-86a9-eab30e9dde20",
"status": "SUCCESS"
},
{
"operationId": "dd2a0f07-bcc2-45fb-bf3f-057b0f5eb5fa",
"status": "SUCCESS"
},
{
"operationId": "c9e979bc-2b03-4b56-91ed-738ffb17dbd4",
"status": "QUEUED"
}
]
},
"extensions": {
"vbnxt-trace-id": "c7415ed35a81b7362c602024ff88b77f"
}
}