非同期タスク

コンポーネントでは、非同期的にしか利用できないデータをレンダリングする必要がある場合があります。そのようなデータは、サーバー、データベース、または一般的な非同期 API から取得または計算される場合があります。

Lit のリアクティブ更新ライフサイクルはバッチ処理され非同期ですが、Lit テンプレートは常に同期的にレンダリングされます。テンプレートで使用されるデータは、レンダリング時に読み取り可能である必要があります。Lit コンポーネントで非同期データをレンダリングするには、データの準備が整うまで待機し、読み取り可能になるように保存してから、データを同期的に使用できる新しいレンダリングをトリガーする必要があります。データの取得中またはデータ取得が失敗した場合に何をレンダリングするかについても、多くの場合考慮する必要があります。

@lit/task パッケージは、この非同期データワークフローの管理に役立つ Task リアクティブコントローラーを提供します。

Task は、非同期タスク関数を取得し、引数が変更されたときに手動または自動的に実行するコントローラーです。Task はタスク関数の結果を保存し、タスク関数が完了するとホスト要素を更新して、結果をレンダリングで使用できるようにします。

これは、fetch() を介して HTTP API を呼び出すために Task を使用する例です。API は productId パラメーターが変更されるたびに呼び出され、データの取得中はコンポーネントが読み込みメッセージをレンダリングします。

Task は、非同期作業の適切な管理に必要な多くのことを処理します。

  • ホストが更新されたときにタスク引数を収集します。
  • 引数が変更されたときにタスク関数を実行します。
  • タスクの状態(初期、保留中、完了、またはエラー)を追跡します。
  • タスク関数の最後の完了値またはエラーを保存します。
  • タスクの状態が変更されるとホストの更新をトリガーします。
  • 競合状態を処理し、最新のタスク呼び出しのみがタスクを完了するようにします。
  • 現在のタスク状態に適切なテンプレートをレンダリングします。
  • AbortController を使用してタスクの中断を許可します。

これにより、コードから非同期データを使用するための定型的な処理の大部分が削除され、競合状態やその他のエッジケースの堅牢な処理が保証されます。

非同期データとは、すぐに利用できないデータですが、将来のある時点で利用できる可能性のあるデータです。たとえば、同期的に使用できる文字列やオブジェクトのような値ではなく、Promise は将来に値を提供します。

非同期データは通常、非同期 API から返されます。これはいくつかの形式で提供されます。

  • fetch() のような Promise や非同期関数
  • コールバックを受け取る関数
  • DOM イベントなど、イベントを発行するオブジェクト
  • Observable やシグナルなどのライブラリ

Task コントローラーは Promise を処理するため、非同期 API の形状に関係なく、Task で使用するために Promise に適応できます。

Task コントローラーの中核にあるのは、「タスク」そのものの概念です。

タスクとは、データを生成して Promise で返す作業を行う非同期操作です。タスクはいくつかの異なる状態(初期、保留中、完了、およびエラー)になり、パラメーターを取ることができます。

タスクは一般的な概念であり、任意の非同期操作を表すことができます。それらは、ネットワークフェッチ、データベースクエリ、または何らかのアクションに応答して単一のイベントを待機するなど、要求/応答構造がある場合に最も有効です。それらは、イベントの無限のストリーム、ストリーミングデータベース応答など、自発的またはストリーミング操作にはあまり適用されません。

Taskリアクティブコントローラー なので、Lit のリアクティブ更新ライフサイクルに応答し、それをトリガーできます。

一般的に、コンポーネントが実行する必要がある各論理タスクに対して 1 つの Task オブジェクトを用意します。クラスのフィールドとしてタスクをインストールします。

クラスフィールドとして、タスクの状態と値は簡単に利用できます。

タスク宣言で最も重要な部分は、タスク関数です。これは、実際の作業を行う関数です。

タスク関数は、task オプションで指定されます。Task コントローラーは、別々の args コールバックで提供される引数を使用して、タスク関数を自動的に呼び出します。引数の変更がチェックされ、引数が変更された場合にのみタスク関数が呼び出されます。

タスク関数は、最初の引数として渡される配列としてタスク引数を受け取り、2 番目の引数としてオプション引数を受け取ります。

タスク関数の args 配列と args コールバックの長さは同じである必要があります。

this 参照がホスト要素を指すように、task 関数と args 関数をアロー関数として記述します。

タスクは 4 つの状態のいずれかになります。

  • INITIAL: タスクは実行されていません。
  • PENDING: タスクは実行中で、新しい値を待っています。
  • COMPLETE: タスクは正常に完了しました。
  • ERROR: タスクでエラーが発生しました。

Task の状態は、Task コントローラーの status フィールドで利用でき、INITIALPENDINGCOMPLETE、および ERROR のプロパティを持つ TaskStatus enum のようなオブジェクトによって表されます。

通常、Task は INITIAL から PENDING に、COMPLETE または ERROR のいずれかに進み、タスクが再実行されると PENDING に戻ります。タスクの状態が変更されると、ホストの更新がトリガーされ、ホスト要素は必要に応じて新しいタスクの状態を処理してレンダリングできます。

タスクの状態を理解することは重要ですが、通常は直接アクセスする必要はありません。

タスクの状態に関連する Task コントローラーにはいくつかのメンバーがあります。

  • status: タスクの状態。
  • value: 完了した場合のタスクの現在の値。
  • error: エラーが発生した場合のタスクの現在のエラー。
  • render(): 現在の状態に基づいて実行するコールバックを選択するメソッド。

タスクをレンダリングするために使用できる最もシンプルで一般的な API は task.render() です。これは、実行する適切なコードを選択し、関連するデータを提供するためです。

render() は、各タスク状態のオプションのコールバックを含む config オブジェクトを受け取ります。

  • initial()
  • pending()
  • complete(value)
  • error(err)

Lit の render() メソッド内で task.render() を使用して、タスクの状態に基づいてテンプレートをレンダリングできます。

デフォルトでは、タスクは引数が変更されるたびに実行されます。これは、デフォルトで true に設定されている autoRun オプションによって制御されます。

自動実行モードでは、ホストが更新されたときにタスクは args 関数を呼び出し、args を前の args と比較し、変更があった場合にタスク関数を呼び出します。args が定義されていないタスクは、手動モードです。

autoRun が false に設定されている場合、タスクは手動モードになります。手動モードでは、イベントハンドラーなどから .run() メソッドを呼び出すことでタスクを実行できます。

手動モードでは、新しい引数を run() に直接提供できます。

引数が run() に提供されない場合、args コールバックから収集されます。

前のタスクの実行が保留中の間にもタスク関数を呼び出すことができます。これらの場合、保留中のタスクの実行の結果は無視され、リソースを節約するために、保留中の作業またはネットワーク I/O をキャンセルする必要があります。

タスク関数の第2引数の`signal`プロパティで渡されるAbortSignalを使用して処理できます。保留中のタスクの実行が新しい実行によって置き換えられると、保留中の実行に渡されたAbortSignalが中断され、タスクの実行に保留中の作業をキャンセルするよう信号が送信されます。

AbortSignalは、作業を自動的にキャンセルするわけではありません。単なる信号です。作業をキャンセルするには、信号を確認して自分で行うか、fetch()addEventListener()など、AbortSignalを受け入れる別のAPIに信号を転送する必要があります。

AbortSignalを使用する最も簡単な方法は、fetch()など、それを受け入れるAPIに転送することです。

信号をfetch()に転送すると、信号が中断された場合、ブラウザはネットワークリクエストをキャンセルします。

タスク関数内で信号が中断されたかどうかを確認することもできます。非同期呼び出しからタスク関数に戻った後に信号を確認する必要があります。throwIfAborted()は、これを行うための便利な方法です。

別のタスクが完了したときに1つのタスクを実行したい場合があります。タスクに異なる引数がある場合、チェーンされたタスクは最初のタスクが再度実行されなくても実行できるため、これは便利です。この場合、最初のタスクをキャッシュとして使用します。これを行うには、タスクの値を別のタスクの引数として使用できます。

1つのタスク関数を使用して、中間結果をawaitすることもできます。

タスクの引数の型は、TypeScriptによって緩すぎる場合もあります。これは、as constを使用して引数配列をキャストすることで修正できます。次のタスク(2つの引数を持つ)を考えてみましょう。

記述されているように、タスク関数の引数リストの型はArray<number | string>として推論されます。

しかし、理想的には、引数のサイズと位置が固定されているため、タプル[number, string]として型指定する必要があります。

argsの戻り値はargs: () => [this.myNumber, this.myText] as constとして記述でき、これにより、task関数のargsリストにタプル型が生成されます。