Async queries

Async queries allow for the execution of GraphQL queries and mutations asynchronously, useful for long-running operations that might exceed timeouts.

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.

Mutation schema

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.

AsyncRequest

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:

Query

mutation
{
  asyncQuery(query:"""
  query {
    useCompany(no: 123456)
    {
      generalLedgerAccount(first: 5) {
        totalCount
        items {
          accountNo
          name
        }
      }
    }  
  }
""")
    {
       operationId
       status
    }
}
Result

{
  "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:

Query

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
    }
}
Variables

{
   "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 schema

Query

query fetch_results($oid : String!)
{
   asyncResult(id: $oid)
   {
      operationId
      status
      error
      data
      createdAt
      completedAt
   }
}
Result

{
  "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

query
{
   asyncRequests
   {
      operationId
      status
   }
}
Result

{
  "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"
  }
}
Last modified September 24, 2024