1. ホーム
  2. javascript

[解決済み] ジャバスクリプトのプロミス好奇心

2023-01-02 14:35:26

質問

このプロミスを呼び出すと、出力が関数呼び出しのシーケンスと一致しない。その .then の前に .catch を持つ約束でも .then が後に呼び出されていたにもかかわらずです。その理由は何でしょうか?

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

verifier(5, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

出力

node promises.js
response: true
error: false

どのように解決するのですか?

これはちょっとクールな質問で、真相を知りたいですね。

これをやると

verifier(3,4).then(...)

を実行する前に、イベントループに戻る必要があります。 .catch() ハンドラを実行する前に、イベントループに戻る必要があります。 その余分なサイクルが次のシーケンスを与える。

verifier(5,4).then(...)

を実行する機会があります。 .then() ハンドラの前に .catch() の前にすでにキューに入っていたからです。 .catch() のハンドラがキューに入る前に既にキューに入っており、アイテムは FIFO 順でキューから実行されるからです。


注意点として、もし .then(f1, f2) の代わりに .then().catch() の代わりに フォームを使用すると、追加のプロミスがないため、期待したときに実行され、追加のティックが関与しません。

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response (3,4): ", response),
        (error) => console.log("error (3,4): ", error)
  );

verifier(5, 4)
  .then((response) => console.log("response (5,4): ", response))
  .catch((error) => console.log("error (5,4): ", error));

なお、すべてのメッセージにラベルをつけましたので、どの verifier() の呼び出しがわかるように、すべてのメッセージにラベルをつけました。


ES6 Spec on promise callback ordering とより詳細な説明。

ES6仕様では、プロミスの "jobs"(コールバックは .then() または .catch() ) は、ジョブキューに挿入された時間に基づいて、FIFO 順で実行されます。 FIFOという具体的な名前は出てきませんが、新しいジョブはキューの最後に挿入され、ジョブはキューの先頭から実行されることが指定されています。 これはFIFOの順序付けを実装しています。

PerformPromiseThen (からのコールバックを実行します。 .then() につながる。 EnqueueJob これは、解決または拒否ハンドラが実際に実行されるようスケジュールされる方法です。EnqueueJobは、保留中のジョブがジョブキューの後ろに追加されることを指定します。次に NextJob オペレーションはキューの先頭から項目を引き出します。これにより、Promiseジョブキューからのジョブを処理する際のFIFO順序が保証されます。

つまり、元の質問の例では、コールバックは verifier(3,4) プロミスと verifier(5,4) のプロミスが実行された順番にジョブキューに挿入されます。なぜなら、それらのオリジナルのプロミスは両方とも終了しているからです。 そして、インタプリタがイベントループに戻ると、最初に verifier(3,4) ジョブをピックアップします。 そのプロミスは拒否され、そのためのコールバックは verifier(3,4).then(...) . つまり、このメソッドは verifier(3,4).then(...) が返したプロミスを拒否し、その結果 verifier(3,4).then(...).catch(...) ハンドラが jobQueue に挿入されます。

次に、イベントループに戻り、jobQueueから引き出す次のジョブは verifier(5, 4) ジョブです。 これは解決済みのプロミスと解決ハンドラを持っているので、そのハンドラを呼び出します。 これによって response (5,4): の出力が表示されます。

そして、イベントループに戻り、jobQueueから引き出す次のジョブは verifier(3,4).then(...).catch(...) ジョブが実行され、これによって error (3,4) の出力が表示されます。

が表示されるからです。 .catch() の方がプロミスレベルとしては深いからです。 .then() の方が1つ深いので、このような問題が発生します。 そして、プロミスチェーンは同期ではなく、FIFOオーダーでジョブキューを経由して1つのレベルから次のレベルへトラバースされるためです。


このレベルのスケジューリング詳細に依存することについての一般的な推奨事項

参考までに、一般的に、私はこのレベルの詳細なタイミングの知識に依存しないコードを書くようにしています。 理解することは興味深く、時には便利ですが、コードへの単純な一見無害な変更が相対的なタイミングの変更につながる可能性があるため、壊れやすいコードです。 ですから、このように2つのチェーン間のタイミングが重要な場合、私はこのレベルの詳細な理解に頼るのではなく、自分の望むタイミングを強制的に作り出すような方法でコードを書きたいと思います。