皆さんは Promise
をご存知でしょうか。
Promise
を使った非同期処理は、javascript を学ぶ上で 1 つの大きな壁になる部分ではないかと思います。
しかし、IE のサポート終了が決定し、考慮すべき環境がモダンブラウザのみとなった現在では、Promise
を使って非同期関数を処理する必要はありません。
async/await
を使用することで、非同期処理を同期的な記述で実現することができます。
今回は、IE 廃止時代の kintone における非同期処理の実装方法を紹介します。
記述方法
kintone カスタマイズで最もよく使用するであろう、REST API を使ったレコードの取得を非同期処理の例として使用します。
/**
* 指定したアプリIDのレコードを取得します
*
* @param { string | number } app アプリID
* @return { Promise<any> } REST APIの取得結果
*/
function getRecords(app) {
return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', { app });
}
Promise を使った場合
まずPromise
オブジェクトを使用した場合のコードをご覧ください。
レコード作成時に、特定のアプリからレコードを取得してログを出力するサンプルです。
kintone.events.on(['app.record.create.show'], (event) => {
return getRecords(15)
.then((response) => {
console.log('レコードを取得しました', response);
return event;
})
.catch((error) => {
console.error('レコード取得時にエラーが発生しました', error);
event.error = 'カスタマイズ処理でエラーが発生しました';
return event;
});
});
Promise
オブジェクトを使用した場合、then
,catch
メソッドにそれぞれ非同期処理に成功した場合、失敗した場合の処理を記述します。
また、kintone.events.on
メソッドの第二引数のコールバック関数は、受け取ったevent
オブジェクトを返却する必要があるため、Promise の各メソッドでevent
オブジェクトをreturn
させています。
では、非同期処理の完了後に別の非同期処理を実行したい場合はどうでしょうか。
kintone.events.on(['app.record.create.show'], (event) => {
return getRecords(15)
.then((first) => {
console.log('アプリID:15のレコードを取得しました', first);
return getRecords(16);
})
.then((second) => {
console.log('アプリID:16のレコードを取得しました', second);
return event;
})
.catch((error) => {
console.error('アプリID15, 16いずれかのレコード取得時にエラーが発生しました', error);
event.error = 'カスタマイズ処理でエラーが発生しました';
return event;
});
});
then
メソッドのコールバック関数内で再度Promise
オブジェクトを返すと、次のthen
メソッドで取得することができます。
このようにthen
、catch
で処理をつなげていくことをメソッドチェーンといいます。
今回は結果を受け取った後の処理が簡単なので可読性に大きな影響はないですが、メソッドチェーンのネスト構造や、最終的にどこへreturn
するのか、辿るのは簡単ではありません。
async/await を使用した場合
前述したPromise
を使用した、非同期処理が複数回必要な場合のコードをasync/await
に置き換えると以下のようになります。
kintone.events.on(['app.record.create.show'], async (event) => {
try {
const first = await getRecords(15);
console.log('アプリID:15のレコードを取得しました', first);
const second = await getRecords(16);
console.log('アプリID:16のレコードを取得しました', second);
} catch (error) {
console.error('アプリID15, 16いずれかのレコード取得時にエラーが発生しました', error);
event.error = 'カスタマイズ処理でエラーが発生しました';
}
return event;
});
いかかでしょうか、ネスト構造も可読性も改善していると思います。
コールバック関数の先頭にasync
を追加し、各非同期処理の手前にawait
を記述しています。
このようにすることで、非同期処理を同期処理とほぼ同様の記述方法で実現することが可能です。
例外
Promise は非同期処理の作成に限らず、非同期処理に関する多くの機能を包括するオブジェクトです。
非同期処理の同期的な処理はasync/await
で代替できますが、他の非同期処理を操作するメソッド群は把握しておくと役に立つと思います。
非同期関数の定義
非同期関数を同期的に処理するだけであればPromise
は不要ですが、自分で非同期処理を定義したい場合はPromise
が必要になります。
kintone の javascript カスタマイズではあまり必要になることはありませんが、意図的に遅延を発生させたい場合などに有効です。
以下のコードは画面にボタンを設置し、ボタンクリックから 1 秒経過後に処理を実行するサンプルです。
/**
* 一定時間待機します
* @param { number } ms 待機する時間(ミリ秒)
*/
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms);
}
kintone.events.on('app.record.edit.show', (event) => {
const button = document.createElement('button');
document.body.append(button);
button.addEventListener('click', async (event) => {
// 1秒待機
await wait(1000);
console.log("ボタンを押して1秒が経過しました");
});
return event;
});
Promise.all
Promise.all
メソッドは引数に Promise
オブジェクトを配列で指定し、その全てが完了、またはいずれかがエラーとなったタイミングでデータが返却されます。
分割代入とも相性が良いので、複数の非同期処理結果から後続する処理を実行する必要がある場合に有効です。
async function getFruits() {
// 非同期処理
}
async function getDishes() {
// 非同期処理;
}
async function dinner() {
const [fruits, dishes] = await Promise.all([getFruits(), getDishes()]);
cooking(fruits, dishes);
}
Promise.allSettled
Promise.allSettled
メソッドはPromise.all
と同様に、複数の非同期処理を同時に処理するためのメソッドですが、エラーが発生した場合の挙動に違いがあります。
Promise.all
を使用した場合、指定した非同期処理のいずれか1つがエラーが発生した場合、他の非同期処理の終了を待たずしてエラーがスローされます。
Promise.allSettled
を使用すると、指定した非同期処理のエラーの発生有無にかかわらず、全ての非同期処理が完了するまで待機します。
返り値にはそれぞれstatus
とvalue
があり、status から非同期処理でエラーが発生したかどうかを確認できます。
async function getFruits() {
// 非同期処理
}
async function getDishes() {
// 非同期処理;
}
async function dinner() {
const [fruits, dishes] = await Promise.allSettled([getFruits(), getDishes()]);
if (fruits.status === 'fulfilled' && dishes.status === 'fulfilled') {
cooking(fruits.value, dishes.value);
} else {
shopping();
}
}