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 内のすべての非同期メソッドの名前は、または Item.loadCustomPropertiesAsync メソッドなどDocument.getSelectedDataAsyncBinding.getDataAsync、"Async" で終わる。 "Async" メソッドは呼び出されるとすぐに実行され、後続のスクリプトも続けて実行することができます。 "Async" メソッドに渡す任意のコールバック関数は、データまたは要求された操作の準備が整い次第、すぐに実行されます。 コールバック関数の実行は通常、直ちに行われますが、戻るまでに若干の遅延が生じることがあります。

次の図は、サーバー ベースのWordまたは Excel で開いているドキュメントでユーザーが選択したデータを読み取る "Async" メソッドの呼び出しの実行フローを示しています。 "Async" 呼び出しが行われる時点で、JavaScript 実行スレッドは追加のクライアント側処理を自由に実行できます (図には何も表示されません)。 "Async" メソッドが返されると、コールバックはスレッドでの実行を再開し、アドインはデータにアクセスして何かを行い、結果を表示できます。 Windows または Mac で Office クライアント アプリケーションを操作する場合も、同じ非同期実行パターンが保持されます。

ユーザー、アドイン ページ、アドインをホストする Web アプリ サーバーとの時間の経過に伴うコマンド実行の相互作用を示す図。

リッチ クライアントと 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 プロパティstatus、、および error プロパティはasyncContext、すべての "Async" メソッドに渡されるコールバック関数に同じ種類の情報を返します。 ただし、プロパティに AsyncResult.value 返されるものは、"Async" メソッドの機能によって異なります。

たとえば、 addHandlerAsync ( BindingCustomXmlPartDocumentRoamingSettingsSettings オブジェクトの) メソッドを使用して、これらのオブジェクトで表される項目にイベント ハンドラー関数を追加します。 任意のAsyncResult.valueaddHandlerAsyncメソッドに渡すコールバック関数からプロパティにアクセスできますが、イベント ハンドラーを追加するときにデータまたはオブジェクトがアクセスされないので、アクセスしようとすると、valueプロパティは常に undefined を返します。

一方、 メソッドを Document.getSelectedDataAsync 呼び出すと、ドキュメントで選択したデータがコールバックのプロパティに AsyncResult.value 返されます。 または、 Bindings.getAllAsync メソッドを呼び出すと、ドキュメント内のすべてのオブジェクトの配列が Binding 返されます。 Bindings.getByIdAsync メソッドを呼び出すと、1 つのBindingオブジェクトが返されます。

メソッドの プロパティAsyncAsyncResult.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 メソッドを呼び出します。
  • 最後に、 メソッドに result2 渡されるコールバックのパラメーターを Binding.getDataAsync 使用して、バインド内のデータを表示します。
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 つの匿名関数がインラインで宣言され、 メソッドと getDataAsync メソッドにgetByIdAsync入れ子になったコールバックとして渡されます。 関数は単純でインラインのため、実装の意図は明白です。

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 つの匿名関数を、 と showResultという名前deleteAllDataの関数として書き換えています。 これらの名前付き関数は、 メソッドと deleteAllDataValuesAsync メソッドにgetByIdAsync名前によってコールバックとして渡されます。

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 つのメソッド ( getDataAsyncsetDataAsyncaddHandlerAsyncremoveHandlerAsync) のみがサポートされています。

バインドを操作するための promises パターンは、この形式になります。

Office.select(selectorExpression, onError).BindingObjectAsyncMethod

selectorExpression パラメーターは、 という形式"bindings#bindingId"をとります。ここで、bindingId は、ドキュメントまたはスプレッドシートで以前に作成したバインドの名前 ( id) です (コレクションの "addFrom" メソッドBindingsの 1 つを使用します: addFromNamedItemAsyncaddFromPromptAsync、または addFromSelectionAsync)。 たとえば、セレクター式 bindings#cities は、"cities" の ID を使用してバインドにアクセスすることを指定します。

onError パラメーターは、指定したバインドへのアクセスに関数が失敗した場合selectに、オブジェクトへのアクセスErrorに使用できる型AsyncResultの 1 つのパラメーターを受け取るエラー処理関数です。 次の例は、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 オブジェクトgetDataAsyncでサポートされている 4 つのBindingオブジェクト メソッド (、、setDataAsyncaddHandlerAsync、または removeHandlerAsync) の呼び出しに置き換えます。 これらのメソッドの呼び出しでは追加の promise がサポートされません。 これらは入れ子のコールバック関数パターンを使用して呼び出す必要があります。

オブジェクト promise Binding が満たされた後は、バインディングであるかのように、チェーンされたメソッド呼び出しで再利用できます (アドイン ランタイムは、promise の実行を非同期的に再試行しません)。 オブジェクトの promise を Binding 満たせない場合、アドイン ランタイムは、次にその非同期メソッドの 1 つが呼び出されたときに、バインド オブジェクトへのアクセスを再試行します。

次のコード例では、 関数をselect使用してコレクションから Bindings "cities" を使用idしてバインドを取得し、addHandlerAsync メソッドを呼び出して、バインドの dataChanged イベントのイベント ハンドラーを追加します。

function addBindingDataChangedEventHandler() {
    Office.select("bindings#cities", function onError(){/* error handling code */}).addHandlerAsync(Office.EventType.BindingDataChanged,
    function (eventArgs) {
        doSomethingWithBinding(eventArgs.binding);
    });
}

重要

関数によってOffice.select返されるオブジェクト promise はBinding、オブジェクトの 4 つのメソッドBindingにのみアクセスできます。 オブジェクトの他のメンバーBindingのいずれかにアクセスする必要がある場合は、代わりに プロパティまたは Bindings.getByIdAsyncBindings.getAllAsync メソッドをDocument.bindings使用してオブジェクトを取得するBinding必要があります。 たとえば、オブジェクトのBindingプロパティ (document、、idまたは プロパティ) にアクセスする必要がある場合、または MatrixBinding オブジェクトまたはtypeTableBinding オブジェクトのプロパティにアクセスする必要がある場合は、 または getAllAsync メソッドを使用getByIdAsyncしてオブジェクトをBinding取得する必要があります。

省略可能なパラメーターを非同期メソッドに渡す

すべての "Async" メソッドの一般的な構文は、このパターンに従います。

AsyncMethod(RequiredParameters, [OptionalParameters],CallbackFunction);

すべての非同期メソッドは、1 つ以上の省略可能なパラメーターを含む JavaScript オブジェクトとして渡される省略可能なパラメーターをサポートします。 省略可能なパラメーターを含むオブジェクトは、キーと値を区切る ":" 文字を持つキーと値のペアの順序なしコレクションです。 オブジェクト内の各ペアはコンマで区切られ、ペアのセット全体が中かっこで囲まれます。 キーはパラメーター名であり、値はそのパラメーターに渡す値です。

オプションのパラメーターを含むオブジェクトをインラインで作成することも、オブジェクトを options 作成して options パラメーターとして渡すこともできます。

省略可能なパラメーターをインラインで渡す

たとえば、オプションのパラメーターをインラインで指定して Document.setSelectedDataAsync メソッドを呼び出す場合の構文は、次のようになります。

 Office.context.document.setSelectedDataAsync(data, {coercionType: 'coercionType', asyncContext: 'asyncContext'},callback);

呼び出し元の構文のこの形式では、 coercionTypeasyncContext の 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 のオブジェクトを作成し、そのオブジェクトを optionsoptions 引数として渡すこともできます。

次の例は、オブジェクトを作成する 1 つの方法を options 示しています。ここで 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;

FilterType パラメーターを指定するために使用した場合、次のValueFormat例のようになります。

const options = {};
options["ValueFormat"] = "unformatted";
options["FilterType"] = "all";

注:

オブジェクトを作成するいずれかの方法を使用する options 場合は、名前が正しく指定されている限り、任意の順序で省略可能なパラメーターを指定できます。

次の例は、オブジェクトで省略可能なパラメーターを Document.setSelectedDataAsync 指定して メソッドを呼び出す方法を options 示しています。

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));
        }
    })
}

この関数を待機する必要がある場合は、キーワード (keyword)で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);
                    }
                });
            }
        });
    });
}

関連項目