Javascriptは好奇心を約束します


96

このpromiseを呼び出すと、出力が関数呼び出しのシーケンスと一致しません。との約束が後に呼び出されていたにもかかわらず、.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

34
独立した約束の連鎖の間のタイミングに頼ってはいけません。
ベルギ

回答:


136

これは、根底にあるクールな質問です。

これを行うとき:

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

これは、新しく拒否されたPromiseが.catch()後続のハンドラーを実行する前に、イベントループに戻る別のサイクルを必要とする新しいPromiseを返します。その余分なサイクルは次のシーケンスを与えます:

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

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


.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仕様

ES6仕様では、promise「ジョブ」(.then()またはからのコールバックを呼び出す.catch()ため)は、ジョブキューに挿入されたタイミングに基づいてFIFO順に実行されることが示されています。FIFOを具体的に指定していませんが、新しいジョブがキューの最後に挿入され、ジョブがキューの最初から実行されることを指定します。これはFIFO順序付けを実装します。

PerformPromiseThen(からのコールバックを実行する.then())は、ResolveまたはRejectハンドラーが実際に実行されるようにスケジュールされる方法であるEnqueueJobにつながります。EnqueueJobは、保留中のジョブがジョブキューの最後に追加されることを指定します。次に、NextJob操作は、キューの先頭からアイテムをプルします。これにより、Promiseジョブキューからジョブを処理する際のFIFOの順序が保証されます。

したがって、元の質問の例では、verifier(3,4)promiseのコールバックとpromiseverifier(5,4)が実行された順序でジョブキューに挿入されます。これは、これらの元のpromiseが両方とも実行されているためです。次に、インタプリタがイベントループに戻ると、最初にverifier(3,4)ジョブを取得します。その約束は拒否され、そのためのコールバックはにありませんverifier(3,4).then(...)。したがって、verifier(3,4).then(...)返されるプロミスを拒否し、verifier(3,4).then(...).catch(...)ハンドラーをjobQueueに挿入します。

次に、イベントループに戻り、jobQueueからプルする次のジョブがverifier(5, 4)ジョブです。これには解決済みのpromiseとresolveハンドラーがあるため、そのハンドラーを呼び出します。これにより、response (5,4):出力が表示されます。

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

これ.catch()は、1番目のチェーンのが.then()2番目のチェーンよりもチェーンの1つのプロミスレベルが深いため、報告した順序が発生するためです。また、Promiseチェーンは、同期ではなく、FIFO順にジョブキューを介して1つのレベルから次のレベルにトラバースされるためです。


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

参考までに、私は一般的に、このレベルの詳細なタイミング知識に依存しないコードを書くようにしています。好奇心が強く、理解するのに役立つこともありますが、コードを無害に変更するだけで相対的なタイミングが変わる可能性があるため、壊れやすいコードです。したがって、このように2つのチェーン間でタイミングが重要な場合は、このレベルの詳細な理解に依存するのではなく、タイミングを希望どおりに強制する方法でコードを記述します。


より具体的には、この正確な動作は、これを実装の詳細にするpromiseの仕様のどこにも文書化されていません。インタープリター間(例:Node.js対Edge対Firefox)またはインタープリターのバージョン間(例:ノード12対ノード14)で異なる動作が発生する可能性があります。仕様では、zalgoコードを回避するためにpromiseが非同期で処理されると述べているだけです(IMHOは、非同期コードの可能性のあるタイミングに依存したいというこのような質問をしている人々によって動機付けられたため、誤った
方向に進んでいました

@ slebetman-個別のPromiseからのPromiseコールバックは、キューに挿入された日時に基づいてFIFOと呼ばれ、次のティックまで実行できないことが文書化されていませんか?ここで必要なのはFIFOの順序.then()付けだけのようです。これは、将来のティックで非同期的に解決/拒否する必要がある新しいpromiseを返す必要があるためです。これが、この順序付けにつながります。競合するコールバックのFIFO順序を使用しない実装を知っていますか?
jfriend 0020

3
@slebetman Promises / A +はそれを指定していません。ES6はそれを指定します。(ただし、ES11はの動作を変更しましたawait)。
ベルギ

キューイング順序のES6仕様から。 これは、resolveまたはrejectハンドラーが呼び出されるようにスケジュールされる方法にPerformPromiseThenつながりEnqueueJobます。EnqueueJobは、保留中のジョブがジョブキューの最後に追加されることを指定します。次に、NextJob操作は、キューの先頭からアイテムをプルします。これにより、PromiseジョブキューでのFIFOの順序が保証されます。
jfriend 0020

@Bergi ES11でのこの変更は何awaitですか?リンクで十分です。ありがとう!
ペドロ

49

Promise.resolve()
  .then(() => console.log('a1'))
  .then(() => console.log('a2'))
  .then(() => console.log('a3'))
Promise.resolve()
  .then(() => console.log('b1'))
  .then(() => console.log('b2'))
  .then(() => console.log('b3'))

同じ理由で、出力a1、a2、a3、b1、b2、b3の代わりに、a1、b1、a2、b2、a3、b3が表示されます。その後、promiseが返され、イベントループの最後に移動します。キュー。だから、この「約束のレース」を見ることができます。ネストされたpromiseがある場合も同じです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.