Office アドインにおける非同期プログラミング
重要
この記事は、Office 2013 で導入された Office JavaScript API モデルである Common API に適用されます。 これらの API には、複数の種類の Office アプリケーション間で共通の UI、ダイアログ、クライアント設定などの機能が含まれます。 Outlook アドインは、共通 API、特に メールボックス オブジェクトを通じて公開される API のサブセットのみを使用します。
共通 API は、アプリケーション固有の API でサポートされていないシナリオにのみ使用してください。 アプリケーション固有の API ではなく共通 API を使用する場合については、「Office JavaScript API について理解する」を参照してください。
Office アドイン API で非同期プログラミングが使用される理由 JavaScript はシングルスレッドの言語であるため、スクリプトで実行時間の長い同期プロセスが呼び出されると、そのプロセスが完了するまで後続のすべてのスクリプト実行がブロックされます。 Office Web クライアント (ただしデスクトップ クライアント) に対する特定の操作は、同期的に実行されると実行をブロックする可能性があるため、ほとんどの Office JavaScript API は非同期的に実行するように設計されています。 これにより、Office アドインの応答性と高速性が確実に向上します。 このような非同期メソッドを利用するときは、多くの場合、コールバック関数の記述も必要です。
API 内のすべての非同期メソッドの名前は、 Document.getSelectedDataAsync
、 Binding.getDataAsync
、 Item.loadCustomPropertiesAsync
メソッドなど、"Async" で終わる。 "Async" メソッドは呼び出されるとすぐに実行され、後続のスクリプトも続けて実行することができます。 "Async" メソッドに渡す任意のコールバック関数は、データまたは要求された操作の準備が整い次第、すぐに実行されます。 コールバック関数の実行は通常、直ちに行われますが、戻るまでに若干の遅延が生じることがあります。
次の図は、サーバー ベースの Word または Excel で開いているドキュメントでユーザーが選択したデータを読み取る "Async" メソッドの呼び出しの実行フローを示しています。 "Async" 呼び出しが行われる時点で、JavaScript 実行スレッドは追加のクライアント側処理を自由に実行できます (図には何も表示されません)。 "Async" メソッドが返されると、コールバックはスレッドでの実行を再開し、アドインはデータにアクセスして何かを行い、結果を表示できます。 Windows または Mac で Office クライアント アプリケーションを操作する場合も、同じ非同期実行パターンが保持されます。
リッチ クライアントと Web クライアントの両方でこの非同期設計をサポートすることは、Office アドイン開発モデルの "write once-run cross-platform (一度書けばどんなプラットフォームでも動く)" 設計目的の一部です。 たとえば、Excel on Windows と Excel on the web の両方で実行される単一のコード ベースを使用して、コンテンツアドインまたは作業ウィンドウ アドインを作成できます。
"Async" メソッドのコールバック関数を記述する
コールバック引数として "Async" メソッドに渡す コールバック 関数では、コールバック関数の実行時に AsyncResult オブジェクトへのアクセスを提供するためにアドイン ランタイムが使用する 1 つのパラメーターを宣言する必要があります。 次のように記述することができます。
"Async" メソッドの コールバック パラメーターとして "Async" メソッドの呼び出しに沿って直接書き込んで渡す必要がある匿名関数。
名前付き関数。その関数の名前を "Async" メソッドの コールバック パラメーターとして渡します。
匿名関数は、コードを 1 回だけ使用する場合に便利です。名前がないため、コードの別の部分で参照することはできません。 名前付き関数は、コールバック関数を複数の "Async" メソッドに再利用する場合に便利です。
匿名コールバック関数を記述する
次の匿名コールバック関数は、コールバックが返されるときに AsyncResult.value プロパティからデータを取得する result
という名前の 1 つのパラメーターを宣言します。
function (result) {
write('Selected data: ' + result.value);
}
次の例は、完全な "Async" メソッド呼び出しのコンテキストで、この匿名コールバック関数を Document.getSelectedDataAsync
メソッドに渡す方法を示しています。
最初の coercionType 引数
Office.CoercionType.Text
は、選択したデータを文字列として返すように指定します。2 番目の コールバック 引数は、 メソッドにインラインで渡される匿名関数です。 関数を実行すると、result パラメーターを使用して、
AsyncResult
オブジェクトのvalue
プロパティにアクセスし、ドキュメント内のユーザーが選択したデータを表示します。
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
write('Selected data: ' + result.value);
}
});
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
コールバック関数の パラメーターを使用して、 AsyncResult
オブジェクトの他のプロパティにアクセスすることもできます。 呼び出しの成功または失敗を判断する場合は AsyncResult.status プロパティを使用します。 呼び出しが失敗した場合は AsyncResult.error プロパティを使用して Error オブジェクトにアクセスし、エラーの詳細を確認できます。
getSelectedDataAsync
メソッドの使用方法の詳細については、「ドキュメントまたはスプレッドシートのアクティブな選択範囲に対するデータの読み取りと書き込み」を参照してください。
名前付きコールバック関数を記述する
または、名前付き関数を記述し、その名前を "Async" メソッドの コールバック パラメーターに渡すことができます。 たとえば、前の例は次のように writeDataCallback
という名前の関数を callback パラメーターとして渡すように書き換えることができます。
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
writeDataCallback);
// Callback to write the selected data to the add-in UI.
function writeDataCallback(result) {
write('Selected data: ' + result.value);
}
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
AsyncResult.value プロパティに返される内容の違い
AsyncResult
オブジェクトのasyncContext
、status
、error
プロパティは、すべての "Async" メソッドに渡されるコールバック関数に同じ種類の情報を返します。 ただし、 AsyncResult.value
プロパティに返されるものは、"Async" メソッドの機能によって異なります。
たとえば、 addHandlerAsync
メソッド ( Binding、 CustomXmlPart、 Document、 RoamingSettings、 Settings オブジェクト) を使用して、これらのオブジェクトで表される項目にイベント ハンドラー関数を追加します。
AsyncResult.value
プロパティには、addHandlerAsync
メソッドのいずれかに渡すコールバック関数からアクセスできますが、イベント ハンドラーを追加するときにデータまたはオブジェクトにアクセスしないため、value
プロパティは常に undefined を返します。
一方、 Document.getSelectedDataAsync
メソッドを呼び出すと、ドキュメントで選択したデータがコールバックの AsyncResult.value
プロパティに返されます。 または、 Bindings.getAllAsync メソッドを呼び出すと、ドキュメント内のすべての Binding
オブジェクトの配列が返されます。
Bindings.getByIdAsync メソッドを呼び出すと、1 つのBinding
オブジェクトが返されます。
Async
メソッドの AsyncResult.value
プロパティに返される内容の説明については、そのメソッドのリファレンス トピックの「コールバック値」セクションを参照してください。
Async
メソッドを提供するすべてのオブジェクトの概要については、AsyncResult オブジェクトに関するトピックの下部にある表を参照してください。
非同期プログラミング パターン
Office JavaScript API では、2 種類の非同期プログラミング パターンがサポートされています。
- 入れ子のコールバックの使用
- promise パターンの使用
コールバック関数のある非同期プログラミングでは、多くの場合、2 つ以上のコールバック内に 1 つのコールバックで返された結果を入れ子にすることが必要となります。 その場合、API のすべての "Async" メソッドからの入れ子のコールバックを使用できます。
入れ子のコールバックを使用することは、ほとんどの JavaScript 開発者にとってなじみのあるプログラミング パターンですが、コールバックが深い入れ子になっているコードは読みにくく、理解しにくいものです。 入れ子になったコールバックの代わりに、Office JavaScript API では promises パターンの実装もサポートされています。
注:
現在のバージョンの Office JavaScript API では、promises パターン の組み込み サポートは 、Excel スプレッドシートと Word ドキュメントのバインドのコードでのみ機能します。 ただし、独自のカスタム Promise を返す関数内にコールバックがある他の関数をラップできます。 詳細については、「 Promise を返す関数で共通 API をラップする」を参照してください。
入れ子のコールバック関数を使用する非同期プログラミング
多くの場合、タスクを完了するには、2 つ以上の非同期操作を実行する必要があります。 これを実現するために、1 つの "Async" 呼び出し内で別の呼び出しを入れ子にできます。
次のコード例では、2 つの非同期呼び出しを入れ子にしています。
- 最初に、Bindings.getByIdAsync メソッドが呼び出され、"MyBinding" という名前のドキュメントのバインドにアクセスします。 そのコールバックの
result
パラメーターに返されるAsyncResult
オブジェクトは、AsyncResult.value
プロパティから指定されたバインド オブジェクトへのアクセスを提供します。 - 次に、最初の
result
パラメーターからアクセスするバインド オブジェクトを使用して 、Binding.getDataAsync メソッドを呼び出します。 - 最後に、
Binding.getDataAsync
メソッドに渡されるコールバックのresult2
パラメーターを使用して、バインド内のデータを表示します。
function readData() {
Office.context.document.bindings.getByIdAsync("MyBinding", function (result) {
result.value.getDataAsync({ coercionType: 'text' }, function (result2) {
write(result2.value);
});
});
}
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
この基本的な入れ子になったコールバック パターンは、Office JavaScript API のすべての非同期メソッドに使用できます。
次のセクションでは、非同期メソッドの入れ子のコールバックで匿名関数または名前付き関数を使用する方法を示します。
入れ子になったコールバックに匿名関数を使用する
次の例では、2 つの匿名関数がインラインで宣言され、入れ子になったコールバックとして getByIdAsync
メソッドと getDataAsync
メソッドに渡されます。 関数は単純でインラインのため、実装の意図は明白です。
Office.context.document.bindings.getByIdAsync('myBinding', function (bindingResult) {
bindingResult.value.getDataAsync(function (getResult) {
if (getResult.status == Office.AsyncResultStatus.Failed) {
write('Action failed. Error: ' + asyncResult.error.message);
} else {
write('Data has been read successfully.');
}
});
});
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
入れ子になったコールバックに名前付き関数を使用する
複雑な実装の場合、名前付き関数を使用すると、読みやすく、保守管理がしやすく、再利用しやすくなります。 次の例では、前のセクションの例の 2 つの匿名関数が、 deleteAllData
および showResult
という名前の関数として書き換えられます。 これらの名前付き関数は、 getByIdAsync
に渡され、名前によってコールバックとして deleteAllDataValuesAsync
メソッドに渡されます。
Office.context.document.bindings.getByIdAsync('myBinding', deleteAllData);
function deleteAllData(asyncResult) {
asyncResult.value.deleteAllDataValuesAsync(showResult);
}
function showResult(asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed) {
write('Action failed. Error: ' + asyncResult.error.message);
} else {
write('Data has been deleted successfully.');
}
}
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
promise パターンを使用してバインドのデータにアクセスする非同期プログラミング
コールバック関数を渡し、その関数が戻るのを待ってから実行を続行する代わりに、promise プログラミング パターンを使用すれば、その意図した結果を表す promise オブジェクトがすぐに返されます。 ただし、本物の同期プログラミングとは異なり、実際には Office アドインのランタイム環境が要求を完了できるまでは、約束された結果の履行は実際には延期されます。 要求が履行されない状況に対処するために onError ハンドラーが用意されています。
Office JavaScript API には、既存のバインド オブジェクトを操作するための promises パターンをサポートする Office.select 関数が用意されています。
Office.select
関数に返される promise オブジェクトは、Binding オブジェクトから直接アクセスできる 4 つのメソッド (getDataAsync、setDataAsync、addHandlerAsync、removeHandlerAsync) のみをサポートしています。
バインドを操作するための promises パターンは、この形式になります。
Office.select(selectorExpression, onError).BindingObjectAsyncMethod
selectorExpression パラメーターはフォーム "bindings#bindingId"
を受け取ります。bindingId は、ドキュメントまたはスプレッドシートで以前に作成したバインドの名前 (id
) です (Bindings
コレクションの "addFrom" メソッドのいずれかを使用します:addFromNamedItemAsync
、addFromPromptAsync
、またはaddFromSelectionAsync
)。 たとえば、セレクター式 bindings#cities
では、"cities" の ID を使用してバインドにアクセスすることを指定します。
onError パラメーターは、select
関数が指定されたバインドにアクセスできない場合に、Error
オブジェクトへのアクセスに使用できる、AsyncResult
型の単一パラメーターを受け取るエラー処理関数です。 次の例は、onErrorパラメーターに渡すことができる基本的なエラー処理関数を示しています。
function onError(result){
const err = result.error;
write(err.name + ": " + err.message);
}
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
BindingObjectAsyncMethod プレースホルダーを、promise オブジェクトでサポートされている 4 つのBinding
オブジェクト メソッド (getDataAsync
、setDataAsync
、addHandlerAsync
、または removeHandlerAsync
) への呼び出しに置き換えます。 これらのメソッドの呼び出しでは追加の promise がサポートされません。 これらは入れ子のコールバック関数パターンを使用して呼び出す必要があります。
Binding
オブジェクトの promise が満たされた後は、バインディングであるかのようにチェーンメソッド呼び出しで再利用できます (アドイン ランタイムは、promise の実行を非同期的に再試行しません)。
Binding
オブジェクトの promise を満たせない場合、アドイン ランタイムは、次にその非同期メソッドの 1 つが呼び出されたときに、バインド オブジェクトへのアクセスを再試行します。
次のコード例では、select
関数を使用して、Bindings
コレクションから id
"cities
" を使用してバインドを取得し、addHandlerAsync メソッドを呼び出して、バインドの dataChanged イベントのイベント ハンドラーを追加します。
function addBindingDataChangedEventHandler() {
Office.select("bindings#cities", function onError(){/* error handling code */}).addHandlerAsync(Office.EventType.BindingDataChanged,
function (eventArgs) {
doSomethingWithBinding(eventArgs.binding);
});
}
重要
Office.select
関数によって返されるBinding
オブジェクト promise は、Binding
オブジェクトの 4 つのメソッドにのみアクセスできます。
Binding
オブジェクトの他のメンバーにアクセスする必要がある場合は、代わりに Document.bindings
プロパティと Bindings.getByIdAsync
メソッドまたは Bindings.getAllAsync
メソッドを使用して、Binding
オブジェクトを取得する必要があります。 たとえば、 Binding
オブジェクトのプロパティ ( document
、 id
、または type
プロパティ) にアクセスする必要がある場合、 MatrixBinding オブジェクトまたは TableBinding オブジェクトのプロパティにアクセスする必要がある場合は、 getByIdAsync
または getAllAsync
メソッドを使用して Binding
オブジェクトを取得する必要があります。
省略可能なパラメーターを非同期メソッドに渡す
すべての "Async" メソッドの一般的な構文は、このパターンに従います。
AsyncMethod(
RequiredParameters, [
OptionalParameters],
CallbackFunction);
すべての非同期メソッドは、1 つ以上の省略可能なパラメーターを含む JavaScript オブジェクトとして渡される省略可能なパラメーターをサポートします。 省略可能なパラメーターを含むオブジェクトは、キーと値を区切る ":" 文字を持つキーと値のペアの順序なしコレクションです。 オブジェクト内の各ペアはコンマで区切られ、ペアのセット全体が中かっこで囲まれます。 キーはパラメーター名であり、値はそのパラメーターに渡す値です。
省略可能なパラメーターを含むオブジェクトをインラインで作成するか、 options
オブジェクトを作成し、そのオブジェクトを options パラメーターとして渡します。
省略可能なパラメーターをインラインで渡す
たとえば、オプションのパラメーターをインラインで指定して Document.setSelectedDataAsync メソッドを呼び出す場合の構文は、次のようになります。
Office.context.document.setSelectedDataAsync(data, {coercionType: 'coercionType', asyncContext: 'asyncContext'},callback);
呼び出し元の構文のこの形式では、 coercionType と asyncContext の 2 つの省略可能なパラメーターは、かっこで囲まれた匿名 JavaScript オブジェクトとしてインラインで定義されます。
次の例は、省略可能なパラメーターをインラインで指定して、 Document.setSelectedDataAsync
メソッドを呼び出す方法を示しています。
Office.context.document.setSelectedDataAsync(
"<html><body>hello world</body></html>",
{coercionType: "html", asyncContext: 42},
function(asyncResult) {
write(asyncResult.status + " " + asyncResult.asyncContext);
}
)
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
注:
パラメーター オブジェクトの名前が正しく指定されている限り、省略可能なパラメーターを任意の順序で指定できます。
options オブジェクトで省略可能なパラメーターを渡す
または、メソッド呼び出しとは別に省略可能なパラメーターを指定する options
という名前のオブジェクトを作成し、 options
オブジェクトを options 引数として渡すこともできます。
次の例は、 options
オブジェクトを作成する 1 つの方法を示しています。ここで、 parameter1
、 value1
など、実際のパラメーター名と値のプレースホルダーを指定します。
const options = {
parameter1: value1,
parameter2: value2,
...
parameterN: valueN
};
ValueFormat パラメーターおよび FilterType パラメーターを指定する場合は次のようになります。
const options = {
valueFormat: "unformatted",
filterType: "all"
};
options
オブジェクトを作成する別の方法を次に示します。
const options = {};
options[parameter1] = value1;
options[parameter2] = value2;
...
options[parameterN] = valueN;
ValueFormat
パラメーターと FilterType
パラメーターを指定するために使用した場合、次の例のようになります。
const options = {};
options["ValueFormat"] = "unformatted";
options["FilterType"] = "all";
注:
options
オブジェクトを作成するいずれかの方法を使用する場合は、名前が正しく指定されている限り、任意の順序で省略可能なパラメーターを指定できます。
次の例では、options
オブジェクトで省略可能なパラメーターを指定して、Document.setSelectedDataAsync
メソッドを呼び出す方法を示します。
const options = {
coercionType: "html",
asyncContext: 42
};
document.setSelectedDataAsync(
"<html><body>hello world</body></html>",
options,
function(asyncResult) {
write(asyncResult.status + " " + asyncResult.asyncContext);
}
)
// Function that writes to a div with id='message' on the page.
function write(message){
document.getElementById('message').innerText += message;
}
どちらのオプションのパラメーター例でも、callback パラメーターが最後のパラメーターとして (インラインのオプションのパラメーターまたは options 引数オブジェクトに続けて) 指定されています。 または、インライン JavaScript オブジェクト内または options
オブジェクト内でコールバック パラメーターを指定することもできます。 ただし、 コールバック パラメーターは、 options
オブジェクト (インラインまたは外部で作成) 内の 1 つの場所、または最後のパラメーターとして渡すことができますが、両方を渡すわけではありません。
Promise を返す関数で共通 API をラップする
Common API (および Outlook API) メソッドは Promise を返しません。 そのため、非同期操作が完了するまで await を使用して実行を一時停止することはできません。
await
動作が必要な場合は、明示的に作成された Promise でメソッド呼び出しをラップできます。
基本的なパターンは、Promise オブジェクトをすぐに返し、内部メソッドが完了したときに Promise オブジェクトを 解決 する非同期メソッドを作成するか、メソッドが失敗した場合にオブジェクトを 拒否 することです。 次に簡単な例を示します。
function getDocumentFilePath() {
return new OfficeExtension.Promise(function (resolve, reject) {
try {
Office.context.document.getFilePropertiesAsync(function (asyncResult) {
resolve(asyncResult.value.url);
});
}
catch (error) {
reject(WordMarkdownConversion.errorHandler(error));
}
})
}
この関数を待機する必要がある場合は、 await
キーワードを使用して呼び出すか、 then
関数に渡すことができます。
注:
この手法は、アプリケーション固有のオブジェクト モデルで run
関数の呼び出し内で Common API を呼び出す必要がある場合に特に便利です。 この方法で使用されている getDocumentFilePath
関数の例については、 サンプル Word-Add-in-JavaScript-MDConversion のファイルHome.js を参照してください。
TypeScript の使用例を次に示します。
readDocumentFileAsync(): Promise<any> {
return new Promise((resolve, reject) => {
const chunkSize = 65536;
const self = this;
Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: chunkSize }, (asyncResult) => {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
reject(asyncResult.error);
} else {
// `getAllSlices` is a Promise-wrapped implementation of File.getSliceAsync.
self.getAllSlices(asyncResult.value).then(result => {
if (result.IsSuccess) {
resolve(result.Data);
} else {
reject(asyncResult.error);
}
});
}
});
});
}
関連項目
Office Add-ins