0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Promise.allで失敗した後も処理が続く:部分成功と中止を分けて設計する

0
Posted at

三つのAPIをまとめて読み込み、一つが失敗したのでエラー画面にした。ところがサーバーログを見ると、残りの処理はまだ動いている。もう一度読み込むと、前の処理と次の処理が重なってしまう。

Promise.allの失敗は、すでに始まった仕事を全部取り消す合図ではありません。集めていた結果を、全件成功として受け取れなくなったという通知です。この違いを知らないと、画面の失敗と裏側の中止を混同してしまいます。

三つ目の処理を手動で完了させる実験で、allの失敗後にも処理が続くことを確認します。ゴールは、全件が必要なのか、部分結果を使うのか、処理そのものを中止したいのかを分けて、集約方法を選べることです。

Promiseとasync/awaitを使える方が対象です。執筆時はWindows・Node.js v24.19.0で実行し、その後Linux・Node.js v24.19.0(2026年11月5日)でも同じ出力を再確認しました。外部APIは使わず、時間差やネットワークの偶然に依存しない形で観察します。

先に決めるのは「一件失敗した画面をどうするか」

たとえば、請求額を確定するために必須のデータが三つあるなら、一つ欠けた合計を完成値として見せられません。一方、独立した三つのダッシュボードなら、失敗したパネルだけ再読み込みにして、成功した二つを使いたいかもしれません。

必要な結果 検討する方法 別途決めること
全件が成功した値のまとまり Promise.all 一件失敗した後、残りをどう扱うか
成功・失敗を含む各件の一覧 Promise.allSettled 失敗した項目の表示と再実行
開始済みの処理そのものを止めたい 各処理に対応するキャンセル 中止要求が何を止め、何を止めないか

allとallSettledは、結果をどう受け取るかの違いです。キャンセル機能への切り替えではありません。

Aは成功、Bは失敗、Cはまだ終わっていない状態を作る

空の練習用フォルダへ次をaggregate.mjsとして保存し、node aggregate.mjsを実行します。追加パッケージは不要です。

aggregate.mjs
import assert from 'node:assert/strict';
let finishSlow;
let slowFinished = false;
const slow = new Promise(resolve => { finishSlow = resolve; })
  .then(value => { slowFinished = true; return value; });
const tasks = [Promise.resolve('A'), Promise.reject(new Error('B failed')), slow];
const all = Promise.all(tasks);
const settled = Promise.allSettled(tasks);
await assert.rejects(all, /B failed/);
assert.equal(slowFinished,false);
console.log('all rejected; C is still unfinished');
finishSlow('C');
const results=await settled;
assert.equal(slowFinished,true);
assert.deepEqual(results.map(x=>x.status),['fulfilled','rejected','fulfilled']);
assert.equal(results[0].value,'A'); assert.equal(results[2].value,'C');
console.log('allSettled:',results.map(x=>x.status).join(', '));
console.log('values:',results[0].value,results[1].reason.message,results[2].value);
console.log('PASS: remaining work completed after aggregate rejection');

コードのfinishSlowは、Cを完了させるために外へ取り出したresolve関数です。本番向けのAPI設計例ではなく、「まだ終わらせていない処理がある」と確実に分かるようにする実験装置です。

まずAは成功、Bは失敗し、Cだけを未完了にします。Promise.allの失敗を確認した時点では、slowFinishedはfalseです。その後にfinishSlow('C')を呼ぶと、Cは完了し、allSettledの結果へ入ります。

実際の出力です。

$ node aggregate.mjs
all rejected; C is still unfinished
allSettled: fulfilled, rejected, fulfilled
values: A B failed C
PASS: remaining work completed after aggregate rejection

最初の行でallは失敗しています。それでも最後には、Cの結果まで得られています。失敗した集約が復活したのではなく、個々の処理が別々に進み、その完了をallSettledでも待っていたということです。

結果配列は、速く終わった順ではなく入力順に読む

allSettledの各要素にはstatusがあります。fulfilledならvalue、rejectedならreasonを読みます。この例では、A・B・Cの入力順で、成功・失敗・成功が返ります。

実アプリなら、各リクエストに対応するIDや表示欄を同じ順序で保持しておくと、どの失敗をどこへ表示するかが決められます。失敗した要素にまでvalueがあるつもりで扱わず、まずstatusで枝を分けます。

この実験でallとallSettledに渡しているのは、同じPromise配列です。結果を二通りで観察しているだけで、同じAPIを二回呼んでいるわけではありません。一方、API呼び出し関数をそれぞれの配列生成で二回実行すれば、仕事そのものが二回始まります。Promiseを渡すことと、仕事を始める関数を呼ぶことを区別してください。

allSettledへ変えただけでは、待ち続ける問題は解決しない

allSettledは全件が成功か失敗で落ち着くまで待ちます。そのため、永遠に完了しないものが一つあると、結果一覧も返りません。今回ならfinishSlow('C')を呼ばなければ、Cの完了を観察できません。

実アプリで長く待つことが問題なら、各処理がいつ打ち切られるべきかを別に決めます。必要に応じてタイムアウトやキャンセルを設計しますが、allSettledへ置き換えただけでそれらが追加されるわけではありません。

なお、allの失敗を誰も受け取らないままにすると、Node.js v24では未処理のrejectionとしてプロセスが終了します(このコードでawait assert.rejects(all, ...)の行を消して試すと確認できます)。「失敗しても後で見ればよい」ではなく、集約したPromiseは必ずawaitやcatchで受け取ります。

また、allのcatchで直ちに全件を再実行すると、旧Cが動いている間に新しいCを始める可能性があります。データを読むだけの処理と、決済や登録など副作用を持つ処理では、再実行の判断が異なります。

想定と違う結果になったら、集約と個別処理を分けて追う

症状 先に確かめること
allの失敗後にもサーバーの処理が続く 集約の失敗と個別の中止を混同していないか。中止処理を実装したか
allSettledでいつまでも結果が返らない 未完了のPromiseがどれか。各処理の完了・失敗の観測があるか
結果が違うパネルへ表示される 入力順と表示先IDの対応。完了順と誤認していないか
リクエストが二重に送られる Promiseの再利用ではなく、生成関数を再度呼んでいないか
失敗を全部無視して画面が完成扱いになる 部分成功を許す項目と、必須項目を分けているか

本記事で実行したのはPromiseの集約です。fetchのAbortSignalや、キャンセル後にサーバー側処理がどこまで止まるかは検証していません。ブラウザ側で待つのをやめたことを、サーバーの処理が取り消された証拠にはできません。

自分の画面へ戻ったら、まず各データを「全部必要」「部分的に使える」に分けてください。その後で、中止と再実行を別の項目として決めます。結果の集め方と仕事の寿命を分けると、一件失敗した後の動きまで説明できる設計になります。

参考資料(一次情報)


このテーマを自分でも試して学びたい方へ。プログラミング学習サービスのCodeNestでは、AIに質問しながらコードを書いて学べます(運営:Hatch IT)。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?