Asynchronous Processing

Overview

This article introduces how to use Kintone's kintone.api() method to run Kintone REST APIs.

This method can be used in the following three ways:

Method 1: Using Callbacks

A callback is a function that is passed as an argument to another function.

kintone.api() chooses which of the callbacks to call, depending on the success or failure of the API. Specify the success callback function as the fourth argument and the error callback function as the fifth argument.

1
kintone.api(API_path, method, request_parameter, success_callback, error_callback)

Below is an example of writing callback functions when running the Get Record API. This code retrieves data from an example Product App within Kintone and displays the record contents in the console.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    function successCallback(resp) {
      const itemRecord = resp.record;
      console.log(itemRecord); // display the contents of the retrieved record to the console
    }
    function failureCallback(err) {
      console.error(err);
    }
    // Kintone REST API Request calling the Kintone Get Record API
    // Pass the callback function called on success as the fourth argument, and called on failure as the fifth argument.
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}, successCallback, failureCallback);
    return event;
  });
})();

When the REST API request succeeds, the successCallback function is called.

In some cases, named functions such as successCallback or failureCallback do not need to be specified, as shown in the code below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    // Kintone REST API Request calling the Kintone Get Record API
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}, (resp) => {
      const itemRecord = resp.record;
      console.log(itemRecord); // display the contents of the retrieved record to the console
    }, (err) => {
      console.error(err);

    });
    return event;
  });
})();

There are two things to be careful about when using callbacks in the kintone.api() method:

  • The function will proceed to the next line of code without waiting for the asynchronous method to finish
  • Callback hell is likely to occur when callbacks are performed many times

More details are explained below.

The function will proceed to the next line of code without waiting for the asynchronous method to finish

kintone.api() is an asynchronous method. Although callbacks will run after the asynchornous call gives back a response, the line after kintone.api() in the code will run without waiting for the asynchronous call to finish.

For example, let's retrieve a record from the Product App, update the event object, and return the event object so that the "price" field is saved into the Quotation App. The following sample updates the event object within the callback, but will result in the "price" field being empty.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Quotation app customization
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', (event) => {
    // Get record from Product app
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}, (resp) => {
      const itemRecord = resp.record;
      // Set the value of "price" to "amount"
      event.record.price.value = itemRecord.amount.value;
    }, (err) => {
      console.error(err);
    });
    return event;
  });
})();

In the above example, the code runs kintone.api(), which is an asynchronous method. As asynchronous methods run simultaneously, the next line of code, return event, runs before kintone.api() finishes the callback. The flow is as follows:

  • kintone.api() runs and makes a call to the /k/v1/record.json endpoint
  • return event runs
  • The response from /k/v1/record.json is received, thus kicks off the callback function
  • const itemRecord = resp.record; is run
  • event.record.price.value = itemRecord.amount.value; is run

The event object was returned before it was updated. Therefore, no changes were made to the "price" field.

Callback hell

The nesting of callbacks can lead to a phenomenon commonly known as callback hell.

In the following example, kintone.api() is used to retrieve three records from a Product app one after another, using callbacks. The nested callbacks are stacked below one after another, forming a pyramid structure, making the code difficult to read and maintain. This scenario is termed callback hell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    const itemAppId = 1;
    kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 1}, (resp1) => {
      // Get record with record ID 1
      console.log(resp1.record.productName.value);
      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {app: itemAppId, id: 2}, (resp2) => {
        // Get record with record ID 2
        console.log(resp2.record.productName.value);
        kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {app: itemAppId, id: 3}, (resp3) => {
          // Get record with record ID 3
          console.log(resp3.record.productName.value);
        }, (err3) => {
          console.error(err3);
        });
      }, (err2) => {
        console.error(err2);
      });
    }, (err1) => {
      console.error(err1);
    });
  });
})();

These can be solved using Method 2: Using Promises and Method 3: Using Async/Await.

Method 2: Using Promises

To solve these problems with asynchronous calls and callbacks, use Promises.

A Promise is an object representing the eventual completion or failure of an asynchronous operation. Promises can be used with the kintone.api() method.

Resolve the problem of functions proceeding to the next line of code without waiting for asynchronous methods to finish

The following example is the code of The function will proceed to the next line of code without waiting for the asynchronous method to finish problem rewritten using Promises.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', (event) => {
    // Get record from Product app
    return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}).then((resp) => {
      const itemRecord = resp.record;
      // Set the value of the Quotation app's "price" to the Product app's "amount"
      event.record.price.value = Number(itemRecord.amount.value);
      return event;
    }).catch((err) => {
      console.error(err);
    });
  });
})();

By omitting the callback function, kintone.api() returns a Promise object.

1
kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1}); // Promise object is returned

Kintone events have a mechanism to wait for the asynchronous methods to finish running if the Promise object is returned (note that not all events are supported).

In the example code above, the event waits for kintone.api() to finish. One of the two callback methods within the then() method then runs, depending on the success or failure of the call. If the call succeeds, the promise is "fulfilled", and the record of the Quotation app will be updated. If the call fails, the promise is "rejected", and the error details are displayed in the console.

Resolve callback hell using Promises

Promises can solve callback hell. The following example is the code of the Callback hell problem rewritten using Promises.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', (event) => {
    const itemAppId = 1;
    // Get record from Product app
    return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 1}).then((resp1) => {
      // Get record with record ID 1
      console.log(resp1.record.productName.value);
      return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 2});
    }).then((resp2) => {
      // Get record with record ID 2
      console.log(resp2.record.productName.value);
      return kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 3});
    }).then((resp3) => {
      // Get record with record ID 3
      console.log(resp3.record.productName.value);
      return event;
    });
  });
})();

Compared to the previous callback hell code, the nesting is shallower, and the code is easier to read. The return kintone.api() in line 6, 9 and 13 returns Promise objects. By returning a Promise object in the event handler, the next operation can be processed after waiting for asynchronous processes in the event handler to finish.

Method 3: Using Async/Await

Async/await is also a method to escape the callback hell. It serves as a better way of approaching promises because it is removing the usage of then() method, which again depends on callbacks.

Resolve function not waiting for callbacks to finish

The following example is the code of Resolve the problem of functions proceeding to the next line of code without waiting for asynchronous methods to finish rewritten using async/await.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', async (event) => {
    try {
      const itemResp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: 1, id: 1});
      const itemRecord = itemResp.record;
      // set Quotation app's "price" to Product app's "amount"
      event.record.amount.value = Number(itemRecord.price.value);
    } catch (err) {
      console.error(err);
    }
    return event;
  });
})();

There are two important points to note when using async/await:

  • Functions using async are where await can be used
  • The operator await needs to be added at the beginning of an asynchronous function.

Therefore, in the above example, the following codes were implemented:

  • async was added to the kintone.events.on callback function
  • await was added at the beginning of the asynchronous kintone.api method

Resolve callback hell using Async/Await

The following example is the code of Resolve callback hell using Promises rewritten using async/await.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(() => {
  'use strict';
  kintone.events.on('app.record.create.show', async (event) => {
    try {
      const itemAppId = 1;
      // Get record from Product app
      const item1Resp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 1});
      console.log(item1Resp.record.productName.value);
      const item2Resp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 2});
      console.log(item2Resp.record.productName.value);
      const item3Resp = await kintone.api(kintone.api.url('/k/v1/record.json', true), 'GET', {app: itemAppId, id: 3});
      console.log(item3Resp.record.productName.value);
    } catch (err) {
      console.error(err);
    }
    return event;
  });
})();

The process that was connected with then() is eliminated, making the code clearer and easier to read.

Reference