代表的な面接トピック

コーディング面接:キャンセル機能を備えた並行数制限付き非同期 map の実装

コーディング普通
Offer.cc 編集チーム公開日 更新日

質問

最大 N 個のアクティブな mapper 呼び出し、入力順の結果、フェイルファストまたはエラー収集、および AbortSignal によるキャンセルを備えた asyncMap(items, mapper, options) を実装してください。エッジケースと計算量についても説明してください。

プロンプトとコンテキスト

このコーディング問題は、すべての Promise を単に Promise.all に渡すのではなく、並行処理のスケジューリングをテストします。候補者は、スループット、順序付け、エラーの伝播、キャンセル、空の入力に関する規約を定義し、制限が決して超えられず、どの要素も二重に消費されないことを示す必要があります。

面接官が評価するポイント

  • 固定ワーカーまたは同等のスケジューラがアクティブなタスクを最大 N 個に保っているか。
  • 完了順によって出力の順序が狂わないよう、結果が入力インデックスに基づいて書き込まれているか。
  • すでに実行中のタスクを含め、フェイルファストとエラー収集のセマンティクスが明示的であるか。
  • 無効な N、空の入力、同期的な例外のスロー、キャンセル、および非 Promise の mapper 戻り値が適切に機能するか。

質問して明確にすべき点

キャンセル後もタスクを継続できるかどうか、フェイルファストがクリーンアップを待つかどうか、部分的な結果が返されるかどうか、および mapper が AbortSignal を受け付けるかどうかを確認します。リトライ、動的な並行数変更、元のエラーオブジェクトの同一性保持についても質問します。

30秒の回答フレームワーク

次のインデックス番号を共有する最大 N 個のワーカーを作成します。各ワーカーはインデックスを取得し、同期的な戻り値とスローを Promise.resolve でラップして、インデックスに基づいて結果を書き込みます。フェイルファストは最初に検出されたエラーを伝播して新規ディスパッチを停止し、収集モードは各ステータスを記録します。キャンセルは新規のインデックス取得を停止し、シグナルを mapper に渡します。作業量は O(items)、結果およびステータスの空間は O(items)、アクティブな作業は O(N) に制限されます。

ステップバイステップの詳細解説

1. 結果とエラーの規約を定義する

結果は入力順序を維持します。収集モードは、元の値または理由とともに fulfilled または rejected のステータスを返します。undefined を成功の目印として使用することはできません。フェイルファストは最初に検出されたエラーを返しますが、JavaScript ではすでに開始された Promise を強制終了できないことに留意してください。シグナルを受け付ける mapper は、キャンセル処理と協調できます。

2. 固定ワーカーとインデックス割り当てを使用する

次のインデックスを共有状態として保持します。各ワーカーは、インデックスが範囲外になるか、キャンセルが設定されるか、フェイルファストによってディスパッチが停止されるまでループします。タスクを開始する前にインデックスをインクリメントし、1つのアイテムが1度だけ取得されるようにします。アイドル状態の Promise を避けるために min(N, items.length) 個のワーカーを使用します。

3. 同期例外と完了順序を処理する

同期的な mapper のスローをキャッチし、Promise.resolve を使用して値と Promise の両方を正規化します。成功または失敗を元のインデックスに書き込みます。完了コールバックから配列に push してはいけません。1つのワーカーが終了するとすぐに次のインデックスを取得し、アクティブな作業を増やすことなくスループットを維持します。

4. キャンセルを伝播しディスパッチを停止する

シグナルがすでにアボートされている場合は、処理を開始する前に拒絶するか、文書化されたキャンセルエラーを返します。実行中のタスクは同じシグナルを受け取ります。スケジューラは、メインの Promise を確定する前にワーカーが終了するのを待つため、バックグラウンドループがディスパッチを継続している間に呼び出し元が完了を検出することはありません。キャンセルとビジネスエラーは明確に区別できる原因を持つ必要があります。

5. エッジケースと計算量を検証する

空の配列、0 の N、入力より大きい N、同期的な値とスロー、さまざまな遅延、最初のエラー、複数のエラー、実行中のキャンセルをテストします。アクティブカウンターを使用してそれが N を超えないことをアサートし、各インデックスが1回だけ実行されることをフックで確認します。mapper の呼び出しは O(items)、結果空間は O(items)、スケジューラの並行数は O(N) です。

優れた回答例

N が正の整数であることを検証し、min(N, items.length) 個のワーカーを作成して、次のインデックスカウンターを共有します。ワーカーはインデックスを取得し、同期的な値とスローが同じパスをたどるように Promise.resolve 経由で mapper を呼び出し、結果をインデックス別に書き込みます。フェイルファストはディスパッチ停止フラグを設定して最初のエラーを伝播し、収集モードは各ステータスを保持します。キャンセルは新規のインデックス取得を防ぎ、アクティブな mapper にシグナルを渡した上で、ワーカーの終了を待ちます。テストでは、順不同の遅延、同期的エラー、空の入力、N の境界値、キャンセルをカバーし、アクティブな作業が N を超えないことをアサートします。

よくある間違い

  • Promise.all(items.map(mapper)) を呼び出して、すべてのタスクを一度に開始してしまう。
  • 結果を push してしまい、出力が入力順ではなく完了順になってしまう。
  • Promise の reject のみをキャッチし、同期的な mapper の例外スローを見落とす。
  • フェイルファスト後もディスパッチを継続する、または reject によって実行中の作業が自動的にキャンセルされると思い込む。
  • キャンセルを通常のビジネスエラーとして扱い、呼び出し元がリトライの動作を選択できなくなる。
  • N、空の入力、および通常の非 Promise な mapper 戻り値のバリデーションを省略する。

フォローアップの質問と回答

なぜ再帰的に次のアイテムを開始しないのですか?

再帰は直列チェーンを表現できますが、並行数、エラー、キャンセルの管理も行わなければなりません。固定ワーカーを使用すると、アクティブタスクの証明がより明確になり、深い再帰や重複ディスパッチを回避できます。

フェイルファスト時にすでに実行中のタスクはどうなりますか?

JavaScript の Promise には一般的な強制終了プリミティブがありません。新規のディスパッチを停止し、mapper が AbortSignal に協調している場合は abort を呼び出します。そうでない場合は、外部ですでに確定された結果へ書き込むことなくタスクを完了させます。

収集モードはどのようにエラーを表現すべきですか?

元の値または理由を保持しながら、fulfilled と rejected を区別するアイテムごとのステータスオブジェクトを返します。成功値自体が undefined や null である可能性があるため、番兵(センチネル)値の使用は安全ではありません。

並行数を動的に調整するにはどうすればよいですか?

設定された最大値をアクティブな作業とは別に追跡し、安全な境界で取得の追加または停止を行います。動的調整は証明とテストのコストを増加させるため、要件が明示的に適応を必要としない限り、固定の制限を使用します。

公開情報ソース

関連する質問

関連面接ツール

コーディング問題にはスクリーンショットを使用

問題をキャプチャし、制約条件、解法アプローチ、コード、エッジケース、計算量の順に進めます。

ツールを見る