jQuery ditangguhkan dan dijanjikan - .then () vs .done ()

473

Saya telah membaca tentang jQuery ditangguhkan dan janji dan saya tidak bisa melihat perbedaan antara menggunakan .then()& .done()untuk panggilan balik yang sukses. Saya tahu Eric Hynds menyebutkan itu .done()dan .success()memetakan ke fungsi yang sama tapi saya kira begitu juga .then()karena semua panggilan balik semua dipanggil pada penyelesaian operasi yang sukses.

Adakah yang bisa tolong beri tahu saya tentang penggunaan yang benar?

screenm0nkey
sumber
15
Harap perhatikan semua orang yang dirilis JQuery 3.0 pada Juni 2016 adalah versi pertama yang sesuai dengan spesifikasi Promises / A + dan ES2015 Promises. Implementasi yang sebelumnya tidak sesuai dengan janji yang seharusnya diberikan.
Flimm
Saya memperbarui jawaban saya dengan rekomendasi yang ditingkatkan untuk apa yang harus digunakan kapan.
Robert Siemer

Jawaban:

577

Callback yang dilampirkan done()akan dipecat ketika ditangguhkan diselesaikan. Callback yang dilampirkan fail()akan dipecat ketika ditangguhkan ditolak.

Sebelum jQuery 1.8, then()itu hanya gula sintaksis:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

Pada 1,8, then()adalah alias untuk pipe()dan mengembalikan janji baru, lihat di sini untuk informasi lebih lanjut tentang pipe().

success()dan error()hanya tersedia pada jqXHRobjek yang dikembalikan oleh panggilan ke ajax(). Itu adalah alias sederhana untuk done()dan fail()masing - masing:

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

Selain itu, done()tidak terbatas pada panggilan balik tunggal dan akan memfilter non-fungsi (meskipun ada bug dengan string di versi 1.8 yang harus diperbaiki di 1.8.1):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

Sama berlaku untuk fail().

Julian Aubourg
sumber
8
thenmengembalikan janji baru adalah hal kunci yang saya lewatkan. Saya tidak bisa mengerti mengapa rantai seperti $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })gagal dengan data2tidak terdefinisi; ketika aku berubah doneuntuk thenbekerja, karena saya benar-benar ingin janji-janji pipa bersama-sama daripada melampirkan lebih penangan untuk janji aslinya.
wrschneider
5
jQuery 3.0 adalah versi pertama yang sesuai dengan spesifikasi Promises / A + dan ES2015.
Flimm
4
Saya masih tidak mengerti mengapa saya akan menggunakan salah satunya. Jika saya melakukan panggilan ajax dan saya harus menunggu sampai panggilan itu sepenuhnya selesai (artinya respons dikembalikan dari server) sebelum saya memanggil panggilan ajax lain, apakah saya menggunakan doneatau then? Mengapa?
CodingYoshi
@CodingYoshi Periksa jawaban saya untuk akhirnya menjawab pertanyaan itu (gunakan .then()).
Robert Siemer
413

Ada juga perbedaan dalam cara pengembalian hasil diproses (disebut chaining, donetidak berantai saat thenmenghasilkan rantai panggilan)

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

Hasil berikut akan dicatat:

abc
123
undefined

Sementara

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

akan mendapatkan yang berikut ini:

abc
abc
abc

---------- Perbarui:

Btw. Saya lupa menyebutkan, jika Anda mengembalikan Janji bukan nilai jenis atom, janji luar akan menunggu sampai janji dalam diselesaikan:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

dengan cara ini menjadi sangat mudah untuk menyusun operasi asinkron paralel atau berurutan seperti:

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

Kode di atas mengeluarkan dua permintaan http secara paralel sehingga membuat permintaan selesai lebih cepat, sementara di bawah permintaan http tersebut dijalankan secara berurutan sehingga mengurangi beban server

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})
Lu4
sumber
121
+1 untuk gagasan yang donetidak melakukan apa pun terhadap hasil yang thenmengubah hasilnya. Poin besar yang terlewatkan oleh yang lain imo.
Shanimal
9
Mungkin perlu disebutkan versi apa dari jQuery ini berlaku, karena perilaku thenberubah dalam 1,8
bradley.ayers
4
+1 Langsung ke intinya. Saya membuat contoh runnable jika ada yang ingin melihat rantai apa dengan campuran donedan thenhasil panggilan masuk
Michael Kropat
7
contoh di atas juga menyoroti bahwa 'selesai' bekerja pada objek janji asli yang dibuat pada awalnya tetapi 'kemudian' mengembalikan janji baru.
Pulak Kanti Bhattacharyya
2
Ini berlaku untuk jQuery 1.8+. Versi yang lebih lama bertindak seperti donecontohnya. Ubah thenke pipedalam pra-1.8 untuk mendapatkan thenperilaku 1.8+ .
David Harkness
57

.done() hanya memiliki satu panggilan balik dan itu adalah panggilan balik yang berhasil

.then() memiliki panggilan balik sukses dan gagal

.fail() hanya memiliki satu panggilan balik yang gagal

jadi terserah Anda apa yang harus Anda lakukan ... apakah Anda peduli apakah itu berhasil atau jika gagal?

Marino Šimić
sumber
18
Anda gagal menyebutkan bahwa 'lalu' menghasilkan rantai panggilan. Lihat jawaban Lu4.
oligofren
Jawaban Anda dari 2011 ... Saat ini nilai kembalinya then()sangat berbeda done(). Seperti then()yang sering dipanggil hanya dengan panggilan balik sukses poin Anda lebih merupakan detail daripada hal utama yang perlu diingat / diketahui. (Tidak bisa mengatakan bagaimana keadaannya sebelum jQuery 3.0.)
Robert Siemer
14

deferred.done ()

menambahkan penangan untuk dipanggil hanya ketika Ditangguhkan diselesaikan . Anda dapat menambahkan beberapa panggilan balik untuk dipanggil.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

Anda juga dapat menulis di atas seperti ini,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then ()

menambahkan penangan yang akan dipanggil ketika Ditangguhkan diselesaikan, ditolak atau masih dalam proses .

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}
Nipuna
sumber
posting Anda tidak menjelaskan bagaimana thenberperilaku jika tidak ada failpanggilan balik yang disediakan - yaitu tidak menangkap failkasus sama sekali
BM
Kasus gagal memunculkan pengecualian yang dapat ditangkap oleh tingkat atas program. Anda juga dapat melihat pengecualian di konsol JavaScript.
David Spector
10

Sebenarnya ada perbedaan yang cukup penting, sejauh jQuery's Deferreds dimaksudkan untuk menjadi implementasi dari Janji (dan jQuery3.0 benar-benar mencoba untuk membawanya ke dalam spesifikasi).

Perbedaan utama antara dilakukan / kemudian adalah itu

  • .done() SELALU mengembalikan nilai Janji / terbungkus yang sama dengan yang dimulainya, terlepas dari apa yang Anda lakukan atau apa yang Anda kembalikan.
  • .then() selalu mengembalikan Janji BARU, dan Anda bertanggung jawab untuk mengendalikan apa yang dijanjikan berdasarkan pada fungsi yang Anda berikan untuk mengembalikannya.

Diterjemahkan dari jQuery ke ES2015 Promises asli, .done()adalah semacam menerapkan struktur "ketuk" di sekitar fungsi dalam rantai Janji, dalam hal itu akan, jika rantai tersebut dalam keadaan "tekad", berikan nilai ke fungsi. ... tetapi hasil dari fungsi itu TIDAK akan mempengaruhi rantai itu sendiri.

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

Keduanya akan log 5, bukan 6.

Perhatikan bahwa saya menggunakan done dan doneWrap untuk melakukan logging, bukan .then. Itu karena fungsi console.log tidak benar-benar mengembalikan apa pun. Dan apa yang terjadi jika Anda lulus. Lalu fungsi yang tidak mengembalikan apa pun?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

Itu akan masuk:

5

tidak terdefinisi

Apa yang terjadi? Ketika saya menggunakan .then dan meneruskannya fungsi yang tidak mengembalikan apa pun, hasilnya implisit adalah "tidak terdefinisi" ... yang tentu saja mengembalikan Janji [tidak terdefinisi] ke metode selanjutnya, yang dicatat tidak terdefinisi. Jadi nilai awal yang kami mulai pada dasarnya hilang.

.then()pada dasarnya adalah bentuk komposisi fungsi: hasil dari setiap langkah digunakan sebagai argumen untuk fungsi di langkah berikutnya. Karena itulah .done dianggap sebagai "tap" -> itu sebenarnya bukan bagian dari komposisi, hanya sesuatu yang menyelinap melihat nilai pada langkah tertentu dan menjalankan fungsi pada nilai itu, tetapi tidak benar-benar mengubah komposisi dengan cara apa pun.

Ini adalah perbedaan yang cukup mendasar, dan mungkin ada alasan bagus mengapa Janji asli tidak memiliki metode .done dilaksanakan sendiri. Kita tidak harus membahas mengapa tidak ada metode .fail, karena itu bahkan lebih rumit (yaitu, .fail / .catch BUKAN mirror dari fungsi .done / .then -> dalam .catch yang mengembalikan nilai telanjang tidak menghasilkan "tetap" ditolak seperti yang diteruskan. lalu, mereka menyelesaikan!)

Dtipson
sumber
6

then()selalu berarti itu akan dipanggil dalam kasus apa pun. Tetapi parameter yang lewat berbeda di versi jQuery yang berbeda.

Sebelum ke jQuery 1.8, then()sama dengan done().fail(). Dan semua fungsi panggilan balik berbagi parameter yang sama.

Tetapi pada jQuery 1.8, then()mengembalikan janji baru, dan jika telah mengembalikan nilai, itu akan diteruskan ke fungsi panggilan balik berikutnya.

Mari kita lihat contoh berikut:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

Sebelum jQuery 1.8, jawabannya harus

result = 3
result = 3
result = 3

Semua resultmembutuhkan 3. Dan then()fungsi selalu meneruskan objek yang ditangguhkan yang sama ke fungsi berikutnya.

Tetapi pada jQuery 1.8, hasilnya harus:

result = 3
result = 7
result = NaN

Karena then()fungsi pertama mengembalikan janji baru, dan nilai 7 (dan ini adalah satu-satunya parameter yang akan diteruskan) diteruskan ke yang berikutnya done(), jadi yang kedua done()menulis result = 7. Yang kedua then()mengambil 7 sebagai nilai adan mengambil undefinedsebagai nilai b, jadi yang kedua then()mengembalikan janji baru dengan parameter NaN, dan yang terakhir done()mencetak NaN sebagai hasilnya.

JasmineOT
sumber
"then () selalu berarti itu akan dipanggil dalam kasus apa pun" - tidak benar. maka () tidak pernah dipanggil dalam kasus kesalahan di dalam Janji.
David Spector
Aspek menarik bahwa a jQuery.Deferred()dapat menerima beberapa nilai, yang diteruskan dengan benar ke nilai pertama .then(). — Agak aneh ... karena semua yang mengikuti .then()tidak dapat melakukannya. (Antarmuka yang dipilih melalui returnhanya dapat mengembalikan satu nilai.) Asli Javascript Promisetidak melakukan itu. (Yang lebih konsisten, jujur ​​saja.)
Robert Siemer
3

Ada pemetaan mental yang sangat sederhana dalam respons yang agak sulit ditemukan di jawaban lain:

BM
sumber
2

Hanya gunakan .then()

Ini adalah kekurangannya .done()

  • tidak bisa dirantai
  • blokir resolve()panggilan (semua .done()penangan akan dijalankan secara sinkron)
  • resolve()mungkin mendapatkan pengecualian dari .done()penangan terdaftar (!)
  • pengecualian dalam .done()setengah membunuh yang ditangguhkan:
    • .done()penangan selanjutnya akan dilewati diam-diam

Saya pikir sementara itu .then(oneArgOnly)selalu mengharuskan .catch()agar tidak ada pengecualian diabaikan dalam hati, tapi itu tidak benar lagi: unhandledrejectionlog peristiwa tidak ditangani.then() pengecualian di konsol (sebagai default). Sangat masuk akal! Tidak ada alasan tersisa untuk digunakan .done()sama sekali.

Bukti

Cuplikan kode berikut mengungkapkan, bahwa:

  • semua .done()penangan akan disebut sinkron pada titikresolve()
    • login sebagai 1, 3, 5, 7
    • login sebelum skrip jatuh ke bawah
  • pengecualian dalam penelepon .done()pengaruhresolve()
    • login melalui tangkap di sekitar resolve()
  • pengecualian melanggar janji dari .done()resolusi lebih lanjut
    • 8 dan 10 tidak masuk!
  • .then() tidak memiliki masalah ini
    • dicatat sebagai 2, 4, 6, 9, 11 setelah utas berubah idle
    • (Lingkungan potongan unhandledrejectiontampaknya tidak ada)

Btw, pengecualian dari .done()tidak dapat ditangkap dengan benar: karena pola sinkron .done(), kesalahan dilemparkan pada titik .resolve()(mungkin kode perpustakaan!) Atau pada .done()panggilan yang melampirkan pelakunya jika ditangguhkan sudah diselesaikan.

console.log('Start of script.');
let deferred = $.Deferred();
// deferred.resolve('Redemption.');
deferred.fail(() => console.log('fail()'));
deferred.catch(()=> console.log('catch()'));
deferred.done(() => console.log('1-done()'));
deferred.then(() => console.log('2-then()'));
deferred.done(() => console.log('3-done()'));
deferred.then(() =>{console.log('4-then()-throw');
    throw 'thrown from 4-then()';});
deferred.done(() => console.log('5-done()'));
deferred.then(() => console.log('6-then()'));
deferred.done(() =>{console.log('7-done()-throw');
    throw 'thrown from 7-done()';});
deferred.done(() => console.log('8-done()'));
deferred.then(() => console.log('9-then()'));

console.log('Resolving.');
try {
    deferred.resolve('Solution.');
} catch(e) {
    console.log(`Caught exception from handler
        in resolve():`, e);
}
deferred.done(() => console.log('10-done()'));
deferred.then(() => console.log('11-then()'));
console.log('End of script.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>

Robert Siemer
sumber
Beberapa hal: 1) Saya melihat apa yang Anda katakan yang donetidak akan dieksekusi jika yang dilakukan sebelumnya memiliki pengecualian. Tapi mengapa itu diabaikan secara diam-diam, maksud saya pengecualian terjadi jadi mengapa Anda mengatakan itu diam. 2) Saya membenci Deferredobjek karena API-nya sangat buruk. Itu terlalu rumit dan membingungkan. Kode Anda di sini juga tidak membantu membuktikan maksud Anda dan terlalu banyak kerumitan yang tidak diperlukan untuk apa yang Anda coba buktikan. 3) Mengapa doneindeks 2, 4, dan 6 dilakukan sebelum tanggal 2 then?
CodingYoshi
Buruk saya, ya pasti berhak memilih. Adapun komentar Anda tentang pengecualian, biasanya itulah cara kerja pengecualian: setelah dimunculkan, kode setelah itu tidak akan dieksekusi. Ditambah dokumentasi jQuery menyatakan bahwa itu hanya akan dieksekusi jika ditangguhkan diselesaikan.
CodingYoshi
@CodingYoshi Situasinya berbeda di sini: Saya hanya berbicara tentang janji / penundaan yang diselesaikan. Saya tidak mengeluh bahwa sisa penangan sukses tidak dipanggil, itu normal. Tapi saya tidak melihat alasan mengapa penangan sukses yang sama sekali berbeda pada janji yang sukses tidak dipanggil. Semua .then()akan dipanggil, pengecualian (pada penangan itu) dinaikkan atau tidak. Namun penambahan / sisa .done()istirahat.
Robert Siemer
@CodingYoshi Saya sangat meningkatkan jawaban saya, jika saya diizinkan untuk mengatakannya. Kode dan teks.
Robert Siemer
1

Ada satu lagi perbedaan vital pada jQuery 3.0 yang dapat dengan mudah menyebabkan perilaku tak terduga dan tidak disebutkan dalam jawaban sebelumnya:

Pertimbangkan kode berikut:

let d = $.Deferred();
d.done(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

ini akan menampilkan:

then
now

Sekarang, ganti done()dengan then()dalam cuplikan yang sama:

var d = $.Deferred();
d.then(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

output sekarang:

now
then

Jadi, untuk segera ditangguhkan ditangguhkan, fungsi yang diteruskan ke done()akan selalu dipanggil secara sinkron, sedangkan argumen yang diteruskan ke then()dipanggil async.

Ini berbeda dari versi jQuery sebelumnya di mana kedua panggilan balik dipanggil secara serempak, sebagaimana disebutkan dalam panduan pemutakhiran :

Perubahan perilaku lain yang diperlukan untuk kepatuhan Janji / A + adalah panggilan balik Deferred .then () selalu disebut secara tidak sinkron. Sebelumnya, jika panggilan balik .then () ditambahkan ke Tangguhan yang sudah diselesaikan atau ditolak, panggilan balik itu akan segera berjalan dan sinkron.

schellmax
sumber
-5

.done()mengakhiri rantai janji, memastikan tidak ada lagi yang bisa melampirkan langkah lebih lanjut. Ini berarti bahwa implementasi janji jQuery dapat membuang pengecualian yang tidak ditangani, karena tidak ada yang bisa menanganinya menggunakan .fail().

Secara praktis, jika Anda tidak berencana untuk melampirkan lebih banyak langkah pada sebuah janji, Anda harus menggunakannya .done(). Untuk detail lebih lanjut, lihat mengapa janji harus dilakukan

gleb bahmutov
sumber
6
Peringatan! Jawaban ini akan benar untuk beberapa implementasi janji tetapi tidak jQuery, di mana .done()tidak memiliki peran penghentian. Dokumentasi mengatakan, "Karena deferred.done () mengembalikan objek yang ditangguhkan, metode lain dari objek yang ditangguhkan dapat dirantai ke yang ini, termasuk metode .done () tambahan". .fail()tidak disebutkan tetapi, ya, itu bisa dirantai juga.
Roamer-1888
1
Buruk saya, tidak memeriksa jQuery
gleb bahmutov
1
@ glebbahmutov - mungkin Anda harus menghapus jawaban ini agar orang lain tidak bingung? Hanya saran yang ramah :)
Andrey
2
Tolong jangan hapus jawabannya, ini dapat membantu orang-orang menghapus kesalahpahaman mereka juga.
Melissa