jQuery trì hoãn và hứa hẹn - .then () vs .done ()


473

Tôi đã đọc về các lời hứa và trì hoãn của jQuery và tôi không thể thấy sự khác biệt giữa việc sử dụng .then()& .done()cho các cuộc gọi lại thành công. Tôi biết Eric Hynds đề cập đến điều đó .done().success()ánh xạ tới cùng chức năng nhưng tôi đoán là như vậy vì .then()tất cả các cuộc gọi lại đều được gọi khi hoàn thành một hoạt động thành công.

Bất cứ ai có thể xin vui lòng khai sáng cho tôi để sử dụng đúng?


15
Xin lưu ý tất cả mọi người rằng JQuery 3.0 được phát hành vào tháng 6 năm 2016 là phiên bản đầu tiên tuân thủ thông số Promise / A + và ES2015 Promising. Việc thực hiện trước đó có sự không tương thích với những gì hứa hẹn sẽ cung cấp.
Flimm

Tôi đã cập nhật câu trả lời của mình với một khuyến nghị được cải thiện về việc nên sử dụng khi nào.
Robert Siemer

Câu trả lời:


577

Các cuộc gọi lại được đính kèm done()sẽ bị hủy khi giải quyết hoãn lại. Các cuộc gọi lại được đính kèm fail()sẽ bị hủy khi bị hoãn lại bị từ chối.

Trước jQuery 1.8, then()chỉ là cú pháp cú pháp:

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

Kể từ 1.8, then()là bí danh pipe()và trả lại một lời hứa mới, xem tại đây để biết thêm thông tin pipe().

success()error()chỉ có sẵn trên jqXHRđối tượng được trả về bởi một cuộc gọi đến ajax(). Chúng là các bí danh đơn giản cho done()fail()tương ứng:

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

Ngoài ra, done()không giới hạn ở một cuộc gọi lại duy nhất và sẽ lọc ra các chức năng không (mặc dù có lỗi với các chuỗi trong phiên bản 1.8 nên được sửa trong 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 );

Cùng đi cho fail().


8
thenTrả lại một lời hứa mới là điều quan trọng tôi đã bỏ lỡ. Tôi không thể hiểu tại sao một chuỗi như $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })thất bại với data2không xác định; Khi tôi thay đổi doneđể thennó hoạt động, bởi vì tôi thực sự muốn thực hiện các lời hứa với nhau hơn là gắn nhiều người xử lý hơn với lời hứa ban đầu.
wrschneider

5
jQuery 3.0 là phiên bản đầu tiên tuân thủ thông số Promise / A + và ES2015.
Flimm

4
Tôi vẫn không hiểu tại sao tôi lại sử dụng cái này hơn cái kia. Nếu tôi thực hiện cuộc gọi ajax và tôi cần đợi cho đến khi cuộc gọi đó hoàn thành (nghĩa là phản hồi được trả về từ máy chủ) trước khi tôi gọi một cuộc gọi ajax khác, tôi có sử dụng donehay thenkhông? Tại sao?
CodingYoshi

@CodingYoshi Kiểm tra câu trả lời của tôi để cuối cùng trả lời câu hỏi đó (sử dụng .then()).
Robert Siemer

413

Cũng có sự khác biệt trong cách xử lý kết quả trả về (được gọi là chuỗi, donekhông chuỗi trong khi thentạo chuỗi cuộc gọi)

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

Các kết quả sau đây sẽ được ghi lại:

abc
123
undefined

Trong khi

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

sẽ nhận được những điều sau đây:

abc
abc
abc

---------- Cập nhật:

Btw. Tôi quên đề cập, nếu bạn trả lại một Lời hứa thay vì giá trị loại nguyên tử, thì lời hứa bên ngoài sẽ đợi cho đến khi lời hứa bên trong giải quyết:

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

theo cách này, nó trở nên rất đơn giản để soạn các hoạt động không đồng bộ song song hoặc tuần tự như:

// 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
})

Đoạn mã trên phát hành song song hai yêu cầu http, do đó làm cho các yêu cầu hoàn thành sớm hơn, trong khi bên dưới các yêu cầu http đó đang được chạy tuần tự, do đó giảm tải máy chủ

// 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
})

121
+1 cho khái niệm donekhông làm gì đối với kết quả thenthay đổi kết quả. Điểm lớn bị bỏ lỡ bởi những người khác imo.
Tối thiểu

9
Có lẽ đáng để đề cập đến phiên bản jQuery này áp dụng cho điều gì, vì hành vi thenđã thay đổi trong 1.8
bradley.ayers

4
+1 Đi thẳng vào vấn đề. Tôi đã tạo một ví dụ có thể chạy được nếu bất kỳ ai muốn xem chuỗi nào có kết quả donethengọi kết quả.
Michael Kropat

7
ví dụ trên cũng nhấn mạnh rằng 'thực hiện' hoạt động trên đối tượng lời hứa ban đầu được tạo ban đầu nhưng 'sau đó' trả lại một lời hứa mới.
Pulak Kanti Bhattacharyya

2
Điều này áp dụng cho jQuery 1.8+. Các phiên bản cũ hoạt động giống như doneví dụ. Thay đổi thenthành pipetrước 1.8 để có thenhành vi 1.8+ .
David Harkness

57

.done() chỉ có một cuộc gọi lại và đó là cuộc gọi lại thành công

.then() có cả cuộc gọi lại thành công và thất bại

.fail() chỉ có một cuộc gọi lại thất bại

Vì vậy, nó phụ thuộc vào bạn những gì bạn phải làm ... bạn có quan tâm nếu nó thành công hay nếu nó thất bại?


18
Bạn không đề cập đến việc 'sau đó' tạo ra các chuỗi cuộc gọi. Xem câu trả lời của Lu4.
oligofren

Câu trả lời của bạn là từ năm 2011 ... Ngày nay, giá trị trả về của chúng then()rất khác so với done(). Như then()thường được gọi chỉ với cuộc gọi lại thành công, quan điểm của bạn là một chi tiết hơn là điều chính cần nhớ / biết. (Không thể nói nó như thế nào trước jQuery 3.0.)
Robert Siemer

14

hoãn lại.done ()

thêm trình xử lý chỉ được gọi khi Trì hoãn được giải quyết . Bạn có thể thêm nhiều cuộc gọi lại để được gọi.

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

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

Bạn cũng có thể viết ở trên như thế này,

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

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

hoãn lại.then ()

thêm trình xử lý được gọi khi Trì hoãn được giải quyết, bị từ chối hoặc vẫn đang được xử lý .

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

bài đăng của bạn không làm rõ cách thencư xử nếu không có failcuộc gọi lại được cung cấp - cụ thể là không nắm bắt được failvụ việc
BM

Trường hợp thất bại làm tăng một ngoại lệ có thể bị bắt bởi cấp cao nhất của chương trình. Bạn cũng có thể thấy ngoại lệ trong bảng điều khiển JavaScript.
David Spector

10

Thực sự có một sự khác biệt khá quan trọng, trong chừng mực mà Trì hoãn của jQuery có nghĩa là một triển khai của Lời hứa (và jQuery3.0 thực sự cố gắng đưa chúng vào thông số kỹ thuật).

Sự khác biệt chính giữa thực hiện / sau đó là

  • .done() LUÔN LUÔN trả về cùng các giá trị Promise / bọc mà nó đã bắt đầu, bất kể bạn làm gì hay trả lại những gì.
  • .then() luôn trả về một Lời hứa MỚI và bạn chịu trách nhiệm kiểm soát những gì Lời hứa đó dựa trên chức năng bạn đã chuyển qua.

Được dịch từ jQuery sang Promise ES2015 bản địa, .done()giống như triển khai cấu trúc "tap" xung quanh một chức năng trong chuỗi Promise, theo đó, nếu chuỗi ở trạng thái "giải quyết", chuyển giá trị cho hàm .. . nhưng kết quả của chức năng đó sẽ KHÔNG ảnh hưởng đến chính chuỗi.

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

Cả hai sẽ đăng nhập 5, không phải 6.

Lưu ý rằng tôi đã sử dụng xong và xongWrap để ghi nhật ký, không phải .then. Đó là bởi vì các hàm console.log không thực sự trả về bất cứ thứ gì. Và điều gì xảy ra nếu bạn vượt qua. Sau đó, một chức năng không trả lại bất cứ điều gì?

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

Điều đó sẽ đăng nhập:

5

chưa xác định

Chuyện gì đã xảy ra? Khi tôi sử dụng .then và truyền cho nó một hàm không trả về bất cứ thứ gì, kết quả ngầm định là "không xác định" ... tất nhiên trả về một Promise [không xác định] cho phương thức tiếp theo, sau đó ghi lại không xác định. Vì vậy, giá trị ban đầu chúng tôi bắt đầu với về cơ bản đã bị mất.

.then()là, tại trung tâm, một dạng của thành phần chức năng: kết quả của mỗi bước được sử dụng làm đối số cho chức năng trong bước tiếp theo. Đó là lý do tại sao .done được coi là "vòi" tốt nhất -> nó không thực sự là một phần của tác phẩm, chỉ là thứ gì đó lén nhìn vào giá trị ở một bước nhất định và chạy một hàm ở giá trị đó, nhưng thực tế không thay đổi các thành phần trong bất kỳ cách nào.

Đây là một sự khác biệt khá cơ bản và có lẽ có một lý do chính đáng tại sao Promise bản địa không có phương thức .done tự thực hiện. Chúng ta không cần phải hiểu tại sao không có phương thức .fail, bởi vì điều đó thậm chí còn phức tạp hơn (cụ thể là .fail / .catch KHÔNG phải là gương của các hàm .done / .then -> trong .catch trả về giá trị trần không "ở lại" bị từ chối như những người được chuyển đến .then, họ giải quyết!)


6

then()luôn luôn có nghĩa là nó sẽ được gọi trong mọi trường hợp. Nhưng các tham số truyền qua là khác nhau trong các phiên bản jQuery khác nhau.

Trước jQuery 1.8, then()bằng done().fail(). Và tất cả các hàm gọi lại chia sẻ cùng một tham số.

Nhưng kể từ jQuery 1.8, then()trả về một lời hứa mới và nếu nó trả về một giá trị, nó sẽ được chuyển vào hàm gọi lại tiếp theo.

Hãy xem ví dụ sau:

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

Trước jQuery 1.8, câu trả lời phải là

result = 3
result = 3
result = 3

Tất cả resultmất 3. Và then()hàm luôn chuyển cùng một đối tượng hoãn lại cho hàm tiếp theo.

Nhưng với jQuery 1.8, kết quả sẽ là:

result = 3
result = 7
result = NaN

Bởi vì then()hàm đầu tiên trả về một lời hứa mới và giá trị 7 (và đây là tham số duy nhất sẽ được truyền) được truyền cho tiếp theo done(), do đó, lần done()ghi thứ hai result = 7. Cái thứ hai then()lấy 7 làm giá trị của avà lấy undefinedgiá trị của b, vì vậy cái thứ hai then()trả về một lời hứa mới với tham số NaN, và cái cuối cùng done()in NaN làm kết quả của nó.


"then () luôn có nghĩa là nó sẽ được gọi trong mọi trường hợp" - không đúng. thì () không bao giờ được gọi trong trường hợp có lỗi bên trong Promise.
David Spector

Khía cạnh thú vị mà a jQuery.Deferred()có thể nhận được nhiều giá trị, mà nó chuyển đúng sang giá trị đầu tiên .then(). Mặc dù hơi lạ một chút ... vì bất kỳ điều gì sau đây .then()cũng không thể làm như vậy. (Giao diện được chọn thông qua returnchỉ có thể trả về một giá trị.) Bản gốc của Javascript Promisekhông làm điều đó. (Điều này phù hợp hơn, thành thật mà nói.)
Robert Siemer

3

Có một bản đồ tinh thần rất đơn giản trong phản ứng mà hơi khó tìm thấy trong các câu trả lời khác:


2

Chỉ sử dụng .then()

Đây là những nhược điểm của .done()

  • không thể bị xiềng xích
  • chặn resolve()cuộc gọi (tất cả các .done()trình xử lý sẽ được thực hiện đồng bộ)
  • resolve()có thể có một ngoại lệ từ các .done()trình xử lý đã đăng ký (!)
  • một ngoại lệ trong một .done()nửa giết chết người trì hoãn:
    • .done()xử lý tiếp theo sẽ được âm thầm bỏ qua

Tôi đã nghĩ tạm thời .then(oneArgOnly)luôn luôn yêu cầu .catch()để không có ngoại lệ nào bị bỏ qua trong âm thầm, nhưng điều đó không còn đúng nữa: unhandledrejectionsự kiện ghi lại các .then()ngoại lệ chưa được xử lý trên bảng điều khiển (như mặc định). Rất hợp lí! Không còn lý do để sử dụng .done()cả.

Bằng chứng

Đoạn mã sau đây tiết lộ rằng:

  • tất cả các .done()trình xử lý sẽ được gọi là đồng bộ tại điểmresolve()
    • đăng nhập là 1, 3, 5, 7
    • đăng nhập trước khi tập lệnh rơi xuống dưới cùng
  • ngoại lệ trong một người gọi .done()ảnh hưởngresolve()
    • đăng nhập thông qua bắt xung quanh resolve()
  • ngoại lệ phá vỡ lời hứa từ .done()giải quyết thêm
    • 8 và 10 không được đăng nhập!
  • .then() không có vấn đề nào trong số này
    • đăng nhập là 2, 4, 6, 9, 11 sau khi luồng không hoạt động
    • (môi trường đoạn trích unhandledrejectiondường như không có)

Btw, các trường hợp ngoại lệ từ .done()không thể được bắt chính xác: do mẫu đồng bộ của .done(), lỗi được đưa ra tại điểm .resolve()(có thể là mã thư viện!) Hoặc tại .done()cuộc gọi gắn thủ phạm nếu đã hoãn giải quyết.

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>


Một vài điều: 1) Tôi thấy những gì bạn đang nói donesẽ không được thực thi nếu việc thực hiện trước đó có ngoại lệ. Nhưng tại sao nó lại âm thầm bị bỏ qua, ý tôi là một ngoại lệ xảy ra vậy tại sao bạn lại nói nó im lặng. 2) Tôi coi thường Deferredđối tượng vì API của nó được thực hiện rất kém. Nó quá phức tạp và khó hiểu. Mã của bạn ở đây không giúp chứng minh quan điểm của bạn và nó có quá nhiều sự phức tạp không cần thiết cho những gì bạn đang cố gắng chứng minh. 3) Tại sao donechỉ số 2, 4 và 6 được thực hiện trước chỉ số 2 then?
CodingYoshi

Xấu của tôi, ya chắc chắn xứng đáng một phiếu bầu. Đối với nhận xét của bạn về ngoại lệ, thông thường đó là cách các ngoại lệ hoạt động: một khi được nêu ra, mã sau khi nó sẽ không được thực thi. Cộng với tài liệu jQuery nói rằng nó sẽ chỉ được thực thi nếu việc trì hoãn được giải quyết.
CodingYoshi

@CodingYoshi Tình hình ở đây rất khác: Tôi chỉ nói về những lời hứa / hoãn lại đã được giải quyết. Tôi không phàn nàn rằng phần còn lại của người xử lý thành công không được gọi, đó là điều bình thường. Nhưng tôi thấy không có lý do tại sao một người xử lý thành công hoàn toàn khác về một lời hứa thành công không được gọi. Tất cả .then()sẽ được gọi, ngoại lệ (trong những người xử lý) nêu ra hay không. Nhưng thêm / .done()nghỉ còn lại .
Robert Siemer

@CodingYoshi Tôi đã cải thiện rất nhiều câu trả lời của mình, nếu tôi được phép nói. Mã và văn bản.
Robert Siemer

1

Có một sự khác biệt quan trọng hơn như jQuery 3.0 có thể dễ dàng dẫn đến hành vi không mong muốn và không được đề cập trong các câu trả lời trước:

Hãy xem xét các mã sau đây:

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>

điều này sẽ xuất ra:

then
now

Bây giờ, thay thế done()bằng then()trong đoạn trích rất giống nhau:

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>

đầu ra là:

now
then

Vì vậy, đối với việc trì hoãn được giải quyết ngay lập tức, hàm được truyền tới done()sẽ luôn được gọi một cách đồng bộ, trong khi mọi đối số được truyền tới then()đều được gọi là không đồng bộ .

Điều này khác với các phiên bản jQuery trước, trong đó cả hai cuộc gọi lại được gọi đồng bộ, như được đề cập trong hướng dẫn nâng cấp :

Một thay đổi hành vi khác cần thiết cho việc tuân thủ Promise / A + là các cuộc gọi lại bị hoãn .then () luôn được gọi là không đồng bộ. Trước đây, nếu một cuộc gọi lại .then () đã được thêm vào Trì hoãn đã được giải quyết hoặc bị từ chối, cuộc gọi lại sẽ chạy ngay lập tức và đồng bộ.


-5

.done()chấm dứt chuỗi hứa hẹn, đảm bảo không có gì khác có thể đính kèm các bước tiếp theo. Điều này có nghĩa là việc triển khai lời hứa của jQuery có thể đưa ra bất kỳ ngoại lệ nào chưa được xử lý, vì không ai có thể xử lý nó bằng cách sử dụng .fail().

Trong điều kiện thực tế, nếu bạn không có kế hoạch đính kèm nhiều bước hơn cho một lời hứa, bạn nên sử dụng .done(). Để biết thêm chi tiết, xem tại sao những lời hứa cần phải được thực hiện


6
Chú ý! Câu trả lời này sẽ đúng cho một số triển khai lời hứa nhưng không phải là jQuery, trong đó .done()không có vai trò kết thúc. Tài liệu nói, "Vì deferred.done () trả về đối tượng bị trì hoãn, các phương thức khác của đối tượng bị trì hoãn có thể được kết nối với phương thức này, bao gồm các phương thức .done () bổ sung". .fail()không được đề cập nhưng, vâng, điều đó cũng có thể bị xiềng xích.
Roamer-1888

1
Thật tệ, tôi đã không kiểm tra jQuery
gleb bahmutov

1
@glebbahmutov - có lẽ bạn nên xóa câu trả lời này để người khác không bị nhầm lẫn? Chỉ là một đề nghị thân thiện :)
Andrey

2
Xin đừng xóa câu trả lời, điều này cũng có thể giúp mọi người giải tỏa những hiểu lầm của họ.
Melissa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.