Mengapa javascript ES6 Promises melanjutkan eksekusi setelah penyelesaian?

99

Seperti yang saya pahami, sebuah promise adalah sesuatu yang dapat menyelesaikan () atau menolak () tetapi saya terkejut saat mengetahui bahwa kode dalam promise terus dijalankan setelah penyelesaian atau penolakan dipanggil.

Saya mempertimbangkan untuk menyelesaikan atau menolak menjadi versi keluar atau kembali yang ramah asinkron, yang akan menghentikan semua eksekusi fungsi langsung.

Adakah yang bisa menjelaskan pemikiran di balik mengapa contoh berikut terkadang menunjukkan console.log setelah panggilan penyelesaian:

var call = function() {
    return new Promise(function(resolve, reject) {
        resolve();
        console.log("Doing more stuff, should not be visible after a resolve!");
    });
};

call().then(function() {
    console.log("resolved");
});

jsbin

Ludwig Van Beethoven
sumber
13
Pertanyaan yang masuk akal, tetapi sekali lagi, JS hanya mengeksekusi satu pernyataan demi satu seperti yang Anda perintahkan. resolve()bukanlah pernyataan kontrol JS yang secara ajaib akan memiliki efek return, itu hanya pemanggilan fungsi, dan ya, eksekusi berlanjut setelahnya.
Ini adalah pertanyaan yang bagus, dan bahkan setelah membaca semua tanggapan, saya tidak yakin tentang praktik terbaik ...
Gabriel Glenn
Saya pikir kesalahpahaman datang dari apa yang sebenarnya Anda akhiri dengan tekad (): janji IS diselesaikan tepat setelah Anda memanggil tekad (), tetapi seperti yang telah dikatakan oleh orang lain, ini tidak berarti bahwa fungsi yang telah menghentikan janji telah menghentikannya tugas juga, jadi itu berlanjut sampai mencapai penghentian "normal".
Giuseppe Bertone

Jawaban:

146

JavaScript memiliki konsep "run to finish" . Kecuali jika terjadi kesalahan, suatu fungsi dijalankan hingga returnpernyataan atau akhirnya tercapai. Kode lain di luar fungsi tidak dapat mengganggu itu (kecuali, sekali lagi, terjadi kesalahan).

Jika Anda ingin resolve()keluar dari fungsi penginisialisasi, Anda harus menambahkannya dengan return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Felix Kling
sumber
Hai Felix - Saya pikir ini hanya sebagian dari cerita - bagian lainnya adalah resolve()fungsi asinkron itu sendiri. Seperti yang kita lihat di jawaban (dihapus) lainnya, beberapa orang percaya bahwa panggilan resolveakan segera menjalankan panggilan balik apa pun.
Alnitak
3
@Alnitak resolveitu sendiri tidak asinkron, itu sepenuhnya sinkron. Meskipun menggunakan ES6 API secara ketat, itu tidak dapat diamati apakah itu sinkron atau asinkron.
Esailija
2
@Esailija ok, mungkin saya kurang jelas. Beberapa orang percaya bahwa panggilan resolveakan mengakibatkan panggilan balik terdaftar segera dipanggil sedemikian rupa sehingga mereka adalah bagian dari tumpukan panggilan saat ini. Itu tidak benar, melainkan hanya mengantri panggilan balik (dan Anda benar, ini tidak asinkron, tetapi itu hanya melakukan hal itu dan segera berakhir)
Alnitak
@ Alnitak: Saya mengerti apa yang Anda katakan. Saya hanya menafsirkannya sebagai mengapa console.logpertunjukan itu muncul di alih-alih mengapa itu muncul dalam urutan itu. Sejauh ini, apa resolvedan bagaimana janji tidak relevan dengan bagaimana saya menafsirkan pertanyaan itu. Namun tentunya tetap penting untuk mengetahui konteks dari promise. Salah satu alasan saya menaikkan suara jawaban Anda :)
Felix Kling
9
@Bergi, di edit Anda, ucapkan "return resol ();" yang tampaknya tidak biasa. Untuk meyakinkan diri sendiri bahwa tidak ada hal penting yang terjadi di sana, saya harus membaca dokumentasi dan melihat bahwa (1) resolusitidak tampak mengembalikan apa pun akibatnya, dan (2) nilai kembalian panggilan balik inisialisasi tidak tampaknya digunakan. Bukankah lebih jelas untuk mengatakan "tekad (); kembali;" dengan demikian menghindari gangguan ini?
Don Hatch
19

Callback yang akan dipanggil saat Anda resolvemembuat janji masih diperlukan oleh spesifikasi agar bisa dipanggil secara asinkron. Ini untuk memastikan perilaku yang konsisten saat menggunakan promise untuk campuran tindakan sinkron dan asinkron.

Oleh karena itu, saat Anda memanggil resolvecallback dalam antrean , dan eksekusi fungsi segera dilanjutkan dengan kode apa pun setelah resolve()panggilan tersebut.

Hanya setelah loop peristiwa JS diberikan kontrol kembali, callback dapat dihapus dari antrian dan benar-benar dipanggil.

Alnitak
sumber
1
Antrian callback didokumentasikan dalam A + Specs atau di ES6?
thefourtheye
5
@thefourtheye: Spesifikasi loop peristiwa sebenarnya adalah bagian dari HTML5 sekarang. ES6 mendefinisikan metode internal yang dipanggil EnqueueJob, yang dipanggil oleh .then.
Felix Kling
@thefourtheye: Sebenarnya, ES6 juga tampaknya mendefinisikan antrian: people.mozilla.org/~jorendorff/… . Saya kira terkait dengan loop acara satu atau lain cara.
Felix Kling
@FelixKling terima kasih atas tautannya - Saya tahu begitulah cara kerjanya, tetapi tidak dapat mengutip pasal dan ayat
Alnitak
2
@FelixKling itu adalah microtasks / macrotasks, berikut adalah bagian dalam spesifikasi yang "menolak" "Ketika tidak ada konteks eksekusi yang berjalan dan tumpukan konteks eksekusi kosong, implementasi ECMAScript menghapus PendingJob pertama dari Antrean Pekerjaan dan menggunakan informasi yang ada di dalamnya untuk membuat konteks eksekusi dan memulai eksekusi operasi abstrak Job terkait. "
Benjamin Gruenbaum