次の方法で共有


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.getSelectedDataAsyncBinding.getDataAsyncItem.loadCustomPropertiesAsync メソッドなど、"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 オブジェクトのasyncContextstatuserrorプロパティは、すべての "Async" メソッドに渡されるコールバック関数に同じ種類の情報を返します。 ただし、 AsyncResult.value プロパティに返されるものは、"Async" メソッドの機能によって異なります。

たとえば、 addHandlerAsync メソッド ( BindingCustomXmlPartDocumentRoamingSettingsSettings オブジェクト) を使用して、これらのオブジェクトで表される項目にイベント ハンドラー関数を追加します。 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、setDataAsyncaddHandlerAsyncremoveHandlerAsync) のみをサポートしています。

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

Office.select(selectorExpression, onError).BindingObjectAsyncMethod

selectorExpression パラメーターはフォーム "bindings#bindingId"を受け取ります。bindingId は、ドキュメントまたはスプレッドシートで以前に作成したバインドの名前 (id) です (Bindings コレクションの "addFrom" メソッドのいずれかを使用します:addFromNamedItemAsyncaddFromPromptAsync、または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 オブジェクト メソッド (getDataAsyncsetDataAsyncaddHandlerAsync、または 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 オブジェクトのプロパティ ( documentid、または 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);

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

次の例は、 options オブジェクトを作成する 1 つの方法を示しています。ここで、 parameter1value1など、実際のパラメーター名と値のプレースホルダーを指定します。

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

関連項目