Promise
Overview
Node.js ドライバーは、非同期 Javascript API を使用して MongoDB クラスターと通信します。
非同期 JavaScript を使用すると、処理スレッドが空くのを待たずに操作を実行できます。これにより、長時間かかる操作を実行するときにアプリケーションが応答しなくなるのを防げます。非同期 Javascript の詳細については、非同期 Javascript に関する MDN Web ドキュメントを参照してください。
このセクションでは、Node.js ドライバーで使用して MongoDB クラスターへのメソッド呼び出しの結果にアクセスできる Promises
について説明します。
Promise
Promise は、非同期メソッド呼び出しによって返されるオブジェクトです。これにより、ラップする操作の最終的な成功または失敗に関する情報にアクセスできます。操作がまだ実行中の場合、Promise は Pending 状態になり、操作が正常に完了した場合は Fulfilled 状態になり、操作で例外がスローされた場合は Rejected 状態になります。Promise および関連用語の詳細については、Promise に関する MDN ドキュメントを参照してください。
findOneAndUpdate()
やcountDocuments()
など、MongoDB クラスターと通信するほとんどのドライバー メソッドは Promise オブジェクトを返し、操作の成功または失敗を処理するロジックがすでに含まれています。
then()
メソッドを追加することで、Promise が Fulfilled または Rejected 状態に達したときに実行される独自のロジックを定義できます。then()
の最初のパラメーターは、Promise がFulfilled状態に達したときに呼び出されるメソッドであり、オプションの 2 番目のパラメーターは、 Rejected状態に達したときに呼び出されるメソッドです。 then()
メソッドは、さらにthen()
メソッドを追加できる Promise を返します。
1 つ以上のthen()
メソッドを Promise に追加すると、各呼び出しはその実行結果を次の呼び出しに渡します。このパターンは Promise 連鎖と呼ばれます。次のコードスニペットは、then()
メソッドを 1 つ追加することによる Promise 連鎖の例を示しています。
collection .updateOne({ name: "Mount McKinley" }, { $set: { meters: 6190 } }) .then( res => console.log(`Updated ${res.result.n} documents`), err => console.error(`Something went wrong: ${err}`), );
Rejected状態への Promise の遷移のみを処理するには、最初のパラメータnull
をthen()
に渡すのではなく、 catch()
メソッドを使用します。catch()
メソッドは、Promise がRejected状態に遷移したときに実行される単一のコールバックを受け入れます。
catch()
メソッドは、しばしばスローされた例外を処理するために Promise 連鎖の最後に追加されます。次のコード スニペットは、Promise 連鎖の末尾にcatch()
メソッドを追加する方法を示しています。
deleteOne({ name: "Mount Doom" }) .then(result => { if (result.deletedCount !== 1) { throw "Could not find Mount Doom!"; } return new Promise((resolve, reject) => { ... }); }) .then(result => console.log(`Vanquished ${result.quantity} Nazgul`)) .catch(err => console.error(`Fatal error occurred: ${err}`));
注意
find()
などのドライバー内の特定のメソッドは、Promise ではなく Cursor
を返します。各メソッドが返すタイプを確認するには、Node.js API ドキュメントを参照してください。
Await
async
関数を使用している場合は、Promise でawait
演算子を使用して、Promise が Fulfilled または Rejected 状態に到達して戻ってくるまで、それ以上の実行を一時停止できます。await
演算子は Promise の解決を待つため、Promise チェーニングの代わりに使用してロジックを順番に実行できます。 次のコード スニペットでは、 await
を使用して、最初の Promise チェーンの例と同じロジックを実行します。
async function run() { ... try { res = await myColl.updateOne( { name: "Mount McKinley" }, { $set: { meters: 6190 } }, ); console.log(`Updated ${res.result.n} documents`); } catch (err) { console.error(`Something went wrong: ${err}`); } }
詳細については、await に関する MDN ドキュメントを参照してください。
操作上の考慮事項
async
メソッドを使用するときによくある間違いの 1 つが、Promise オブジェクトではなく結果の値を取得するために、Promise でawait
演算子を使用するのを忘れることです。次の例を考えてみましょう。hasNext()
を使用してカーソルを反復処理すると、他に結果が存在するかどうかを示すブール値になる Promise が返され、next()
ではカーソルが指している次のエントリに解決される Promise が返されます。
async function run() { ... // WARNING: this snippet may cause an infinite loop const cursor = myColl.find(); while (cursor.hasNext()) { console.log(cursor.next()); } }
hasNext()
の呼び出しは Promise
を返すため、条件ステートメントでは解決される値に関係なく true
が返されます。
次のコードスニペットに示すように、コードを await
に変更すると、next()
への呼び出しのみ、MongoError: Cursor is closed
というエラーがスローされます。
async function run() { ... // WARNING: this snippet throws a MongoError const cursor = myColl.find(); while (cursor.hasNext()) { console.log(await cursor.next()); } }
hasNext()
は next()
の結果が返されるまで呼び出されませんが、hasNext()
の呼び出しでは、前の例と同様に、解決される値ではなくtrue
に相当する Promise が返されます。このコードでは、すでに結果を返して閉じられたカーソルでnext()
の呼び出しが試行されます。
下記の例のようにコードを変更し、hasNext()
への呼び出しをawait
のみにすると、コンソールはドキュメントオブジェクトではなく Promise オブジェクトを表示します。
async function run() { ... // WARNING: this snippet prints Promises instead of the objects they resolve to const cursor = myColl.find(); while (await cursor.hasNext()) { console.log(cursor.next()); } }
次のコードに示すように、hasNext()
メソッドと next()
メソッドの両方の呼び出しの前にawait
を使用して、確実に正しい戻り値で操作できるようにします。
async function run() { ... const cursor = myColl.find(); while (await cursor.hasNext()) { console.log(await cursor.next()); } }