Javascript Menjanjikan rasa ingin tahu

96

Saat saya memanggil janji ini, hasilnya tidak sesuai dengan urutan pemanggilan fungsi. Itu .thendatang sebelum .catch, meskipun janji dengan .thendipanggil setelahnya. Apa alasan untuk itu?

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));

keluaran

node promises.js
response: true
error: false
Gustavo Alves
sumber
34
Anda tidak boleh mengandalkan pengaturan waktu antara rantai janji independen.
Bergi

Jawaban:

136

Ini adalah pertanyaan yang bagus untuk digali.

Saat Anda melakukan ini:

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

yang mengembalikan promise baru yang memerlukan siklus lain kembali ke loop peristiwa sebelum promise yang baru ditolak itu bisa menjalankan .catch()penangan yang mengikutinya. Siklus ekstra itu memberikan urutan berikutnya:

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

kesempatan untuk menjalankan .then()penangannya sebelum baris sebelumnya .catch()karena sudah ada dalam antrian sebelum .catch()penangan dari yang pertama masuk ke antrian dan item dijalankan dari antrian dalam urutan FIFO.


Perhatikan, jika Anda menggunakan .then(f1, f2)formulir sebagai pengganti .then().catch(), itu berjalan seperti yang Anda harapkan karena tidak ada janji tambahan dan dengan demikian tidak ada tanda centang tambahan yang terlibat:

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));

Catatan, saya juga memberi label pada semua pesan sehingga Anda dapat melihat dari verifier()panggilan mana mereka berasal yang membuatnya lebih mudah untuk membaca hasilnya.


ES6 Spec tentang pemesanan panggilan balik janji dan penjelasan lebih detail

Spesifikasi ES6 memberi tahu kita bahwa promise "jobs" (karena memanggil callback dari a .then()atau .catch()) dijalankan dalam urutan FIFO berdasarkan pada saat mereka dimasukkan ke dalam antrean pekerjaan. Itu tidak secara khusus menyebut FIFO, tetapi menentukan bahwa pekerjaan baru dimasukkan di akhir antrian dan pekerjaan dijalankan dari awal antrian. Itu mengimplementasikan pemesanan FIFO.

PerformPromiseThen (yang mengeksekusi callback dari .then()) akan mengarah ke EnqueueJob yang merupakan cara penanganan penyelesaian atau penolakan dijadwalkan untuk dijalankan. EnqueueJob menetapkan bahwa pekerjaan yang tertunda ditambahkan di belakang antrian pekerjaan. Kemudian operasi NextJob menarik item dari depan antrian. Ini memastikan urutan FIFO dalam melayani pekerjaan dari antrian pekerjaan Promise.

Jadi, dalam contoh di pertanyaan awal, kita mendapatkan callback untuk verifier(3,4)promise dan verifier(5,4)promise dimasukkan ke dalam antrean pekerjaan sesuai urutan dijalankannya karena kedua promise asli tersebut sudah selesai. Kemudian, saat penafsir kembali ke loop peristiwa, pertama kali penafsir mengambil verifier(3,4)pekerjaan tersebut. Janji itu ditolak dan tidak ada panggilan balik untuk itu di verifier(3,4).then(...). Jadi, yang dilakukannya adalah menolak janji yang verifier(3,4).then(...)dikembalikan dan itu menyebabkan verifier(3,4).then(...).catch(...)penangan dimasukkan ke dalam jobQueue.

Kemudian, ia kembali ke loop peristiwa dan pekerjaan berikutnya yang ditariknya dari jobQueue adalah verifier(5, 4)pekerjaan tersebut. Itu memiliki janji terselesaikan dan penangan tekad sehingga memanggil penangan itu. Ini menyebabkan response (5,4):keluaran ditampilkan.

Kemudian, ia kembali ke loop peristiwa dan pekerjaan berikutnya yang ditarik dari jobQueue adalah verifier(3,4).then(...).catch(...)pekerjaan tempat menjalankannya dan ini menyebabkan error (3,4)keluaran ditampilkan.

Itu karena .catch()rantai di rantai pertama adalah satu tingkat janji lebih dalam di rantainya daripada .then()di rantai kedua yang menyebabkan pengurutan yang Anda laporkan. Dan, itu karena rantai perjanjian dilintasi dari satu tingkat ke tingkat berikutnya melalui antrean pekerjaan dalam urutan FIFO, tidak sinkron.


Rekomendasi Umum Tentang Mengandalkan Tingkat Detail Penjadwalan Ini

FYI, secara umum, saya mencoba menulis kode yang tidak bergantung pada tingkat pengetahuan pengaturan waktu yang terperinci ini. Meskipun penasaran dan terkadang berguna untuk dipahami, ini adalah kode yang rapuh karena perubahan sederhana yang tampaknya tidak berbahaya pada kode dapat menyebabkan perubahan dalam waktu relatif. Jadi, jika pengaturan waktu sangat penting antara dua rantai seperti ini, maka saya lebih suka menulis kode dengan cara yang memaksa waktu seperti yang saya inginkan daripada bergantung pada tingkat pemahaman terperinci ini.

jfriend00
sumber
Untuk lebih spesifiknya, perilaku persis ini tidak didokumentasikan di mana pun dalam spesifikasi promise yang menjadikannya sebagai detail implementasi. Anda mungkin mendapatkan perilaku yang berbeda antara penafsir (mis. Node.js vs Edge vs Firefox) atau di antara versi penafsir (mis. Node 12 vs Node 14). Spesifikasi hanya mengatakan bahwa janji diproses secara asinkron untuk menghindari kode zalgo (yang IMHO salah kaprahnya BTW karena dimotivasi oleh orang-orang yang mengajukan pertanyaan seperti ini yang ingin bergantung pada waktu kode yang berpotensi tidak sinkron)
slebetman
@slebetman - Apakah tidak didokumentasikan bahwa panggilan balik promise dari promise terpisah disebut FIFO berdasarkan saat dimasukkan ke antrean dan tidak dapat dijalankan hingga tik berikutnya? Tampaknya hanya pemesanan FIFO yang diperlukan di sini karena .then()harus mengembalikan janji baru yang harus diselesaikan / ditolak secara asinkron pada waktu mendatang yang mengarah ke pemesanan ini. Apakah Anda mengetahui penerapan yang tidak menggunakan pengurutan FIFO untuk panggilan balik yang bersaing?
jfriend00
3
@slebetman Promises / A + tidak menjelaskannya. ES6 menentukannya. (ES11 mengubah perilaku await, meskipun).
Bergi
Dari spek ES6 pada antrian order. PerformPromiseThenakan mengarah ke EnqueueJobmana penanganan menyelesaikan atau menolak dijadwalkan untuk dipanggil. EnqueueJob menetapkan bahwa pekerjaan yang tertunda ditambahkan di belakang antrian pekerjaan. Kemudian operasi NextJob menarik item dari depan antrian. Ini memastikan urutan FIFO dalam antrian pekerjaan Promise.
jfriend00
@Bergi Apa perubahan awaitdalam ES11 ini? Tautan sudah cukup. Terima kasih!!
Pedro A
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'))

Alih-alih output a1, a2, a3, b1, b2, b3 Anda akan melihat a1, b1, a2, b2, a3, b3 karena alasan yang sama - setiap kemudian mengembalikan sebuah janji dan pergi ke akhir event-loop antre. Jadi kita bisa melihat "perlombaan janji" ini. Hal yang sama terjadi ketika ada beberapa janji bertingkat.

Tarukami
sumber