Khi nào tôi nên sử dụng phương thức “then” của jQuery deferred và khi nào tôi nên sử dụng phương thức “pipe”?


97

jQuery Deferredcó hai hàm có thể được sử dụng để triển khai chuỗi các hàm không đồng bộ:

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

doneCallbacks Một hàm hoặc một mảng các hàm, được gọi khi Giải quyết xong việc hoãn.
failCallbacks Một hàm hoặc một mảng các hàm, được gọi khi Deferred bị từ chối.

pipe()

deferred.pipe( [doneFilter] [, failFilter] ) Returns: Promise

doneFilter Một chức năng tùy chọn được gọi khi Deferred được giải quyết.
failFilter Một chức năng tùy chọn được gọi khi Deferred bị từ chối.

Tôi biết nó then()đã tồn tại lâu hơn một chút pipe()vì vậy cái sau phải thêm một số lợi ích bổ sung, nhưng sự khác biệt chính xác là gì thì tôi không biết. Cả hai đều có khá nhiều tham số gọi lại giống nhau mặc dù chúng khác nhau về tên và sự khác biệt giữa trả về a Deferredvà trả về Promisecó vẻ nhỏ.

Tôi đã đọc đi đọc lại các tài liệu chính thức nhưng luôn thấy chúng quá "dày đặc" để thực sự quấn lấy đầu tôi và tìm kiếm đã tìm thấy rất nhiều cuộc thảo luận về tính năng này hay tính năng khác nhưng tôi không tìm thấy bất kỳ điều gì thực sự làm rõ sự khác biệt ưu và nhược điểm của từng loại.

Vậy dùng khi nào thì tốt hơn thenvà dùng khi nào thì tốt hơn pipe?


Thêm vào

Câu trả lời tuyệt vời của Felix đã thực sự giúp làm rõ hai chức năng này khác nhau như thế nào. Nhưng tôi tự hỏi liệu có khi nào chức năng của then()được ưu tiên hơn chức năng của không pipe().

Rõ ràng pipe()là nó mạnh hơn then()và có vẻ như cái trước có thể làm bất cứ điều gì mà cái sau có thể làm. Một lý do để sử dụng then()có thể là tên của nó phản ánh vai trò của nó như là sự kết thúc của một chuỗi các chức năng xử lý cùng một dữ liệu.

Nhưng có trường hợp sử dụng nào yêu cầu then()trả lại bản gốc Deferredmà không thể thực hiện được pipe()do nó trả lại bản mới Promisekhông?


1
Tôi đã nghĩ về điều này một lúc, nhưng tbh, tôi không thể nghĩ ra bất kỳ trường hợp sử dụng nào. Nó có thể chỉ là một chi phí để tạo các đối tượng hứa hẹn mới nếu bạn không cần chúng (tôi không biết chúng được liên kết với nhau trong nội bộ như thế nào). Điều đó nói rằng, chắc chắn có những người hiểu rõ hơn về điều này hơn tôi.
Felix Kling

6
Bất cứ ai quan tâm đến câu hỏi này chắc chắn sẽ được quan tâm Ticket # 11010 trên tracker jQuery lỗi: MAKE DEFERRED.THEN == DEFERRED.PIPE NHƯ PROMISE / A
hippietrail

Câu trả lời:


103

jQuery 1.8 .then hoạt động giống như .pipe:

Thông báo về việc deferred.pipe()ngừng sử dụng : Kể từ jQuery 1.8, phương thức này không được dùng nữa. Các deferred.then()phương pháp, thay thế nó, nên được sử dụng để thay thế.

Kể từ jQuery 1.8 , deferred.then()phương thức trả về một lời hứa mới có thể lọc trạng thái và giá trị của một hàm bị trì hoãn thông qua một hàm, thay thế deferred.pipe()phương thức hiện không được dùng nữa .

Các ví dụ dưới đây vẫn có thể hữu ích đối với một số người.


Chúng phục vụ các mục đích khác nhau:

  • .then()được sử dụng bất cứ khi nào bạn muốn làm việc với kết quả của quá trình, tức là như tài liệu cho biết, khi đối tượng hoãn lại được giải quyết hoặc bị từ chối. Nó cũng giống như sử dụng .done()hoặc .fail().

  • Bạn sẽ sử dụng .pipe()(trước) lọc kết quả bằng cách nào đó. Giá trị trả về của một lệnh gọi lại tới .pipe()sẽ được chuyển làm đối số cho lệnh gọi lại donefail. Nó cũng có thể trả về một đối tượng bị hoãn khác và các lệnh gọi lại sau sẽ được đăng ký trên đối tượng hoãn này.

    Đó không phải là trường hợp với .then()(hoặc .done(), .fail()), các giá trị trả về của các lệnh gọi lại đã đăng ký chỉ bị bỏ qua.

Vì vậy, nó không phải là bạn sử dụng một trong hai .then() hoặc .pipe() . Bạn có thể sử dụng .pipe()cho các mục đích tương tự .then()nhưng trò chuyện không giữ được.


ví dụ 1

Kết quả của một số thao tác là một mảng các đối tượng:

[{value: 2}, {value: 4}, {value: 6}]

và bạn muốn tính giá trị nhỏ nhất và tối đa. Giả sử chúng ta sử dụng hai lệnh donegọi lại:

deferred.then(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    var min = Math.min.apply(Math, values);

   /* do something with "min" */

}).then(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    var max = Math.max.apply(Math, values);

   /* do something with "max" */ 

});

Trong cả hai trường hợp, bạn phải lặp lại danh sách và trích xuất giá trị từ mỗi đối tượng.

Sẽ tốt hơn nếu bằng cách nào đó trích xuất các giá trị trước để bạn không phải thực hiện việc này trong cả hai lệnh gọi lại riêng lẻ? Đúng! Và đó là những gì chúng tôi có thể sử dụng .pipe()để:

deferred.pipe(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    return values; // [2, 4, 6]

}).then(function(result) {
    // result = [2, 4, 6]

    var min = Math.min.apply(Math, result);

    /* do something with "min" */

}).then(function(result) {
    // result = [2, 4, 6]

    var max = Math.max.apply(Math, result);

    /* do something with "max" */

});

Rõ ràng đây là một ví dụ được tạo ra và có nhiều cách khác nhau (có thể tốt hơn) để giải quyết vấn đề này, nhưng tôi hy vọng nó minh họa cho vấn đề này.


Ví dụ 2

Hãy xem xét các cuộc gọi Ajax. Đôi khi bạn muốn bắt đầu một lệnh gọi Ajax sau khi lệnh gọi trước đó hoàn thành. Một cách là thực hiện cuộc gọi thứ hai bên trong một donecuộc gọi lại:

$.ajax(...).done(function() {
    // executed after first Ajax
    $.ajax(...).done(function() {
        // executed after second call
    });
});

Bây giờ, giả sử bạn muốn tách mã của mình và đặt hai lệnh gọi Ajax này vào bên trong một hàm:

function makeCalls() {
    // here we return the return value of `$.ajax().done()`, which
    // is the same deferred object as returned by `$.ajax()` alone

    return $.ajax(...).done(function() {
        // executed after first call
        $.ajax(...).done(function() {
            // executed after second call
        });
    });
}

Bạn muốn sử dụng đối tượng hoãn lại để cho phép mã khác gọi makeCallsđến đính kèm các lệnh gọi lại cho lệnh gọi Ajax thứ hai , nhưng

makeCalls().done(function() {
    // this is executed after the first Ajax call
});

sẽ không có hiệu quả mong muốn vì cuộc gọi thứ hai được thực hiện bên trong một donecuộc gọi lại và không thể truy cập từ bên ngoài.

Giải pháp sẽ là sử dụng .pipe()thay thế:

function makeCalls() {
    // here we return the return value of `$.ajax().pipe()`, which is
    // a new deferred/promise object and connected to the one returned
    // by the callback passed to `pipe`

    return $.ajax(...).pipe(function() {
        // executed after first call
        return $.ajax(...).done(function() {
            // executed after second call
        });
    });
}

makeCalls().done(function() {
    // this is executed after the second Ajax call
});

Bằng cách sử dụng, .pipe()bây giờ bạn có thể làm cho nó có thể nối các lệnh gọi lại vào lệnh gọi Ajax "bên trong" mà không làm lộ luồng / thứ tự thực tế của các lệnh gọi.


Nói chung, các đối tượng trì hoãn cung cấp một cách thú vị để tách mã của bạn :)


À vâng, tôi đã bỏ qua điều đó pipecó thể thực hiện lọc mà thenkhông thể làm. Nhưng trong Googling các chủ đề này có vẻ như họ chọn gọi nó pipehơn là filtervì coi bộ lọc là một thứ bổ sung đi kèm với nó trong khi pipechỉ rõ mục đích thực sự của nó hơn. Vì vậy, có vẻ như sẽ có những khác biệt khác ngoài bộ lọc. (Sau đó, một lần nữa tôi thừa nhận tôi không thực sự hiểu được những tính năng lọc thậm chí với các ví dụ của bạn nên. result values;Được return values;bằng cách này?)
hippietrail

Khi tôi nói rằng tôi không hiểu các ví dụ của bạn, nó giống như thế này: Trong ví dụ trên, cả hai .then()nhận được cùng một dữ liệu resultmà bạn lọc mỗi lần; trong khi trong ví dụ dưới, hàm .pipe()loại bỏ một số dữ liệu trong nó resulttrước khi chuyển nó vào khi resulthai .then()s tiếp theo sẽ nhận được?
hippietrail

1
@hippietrail: Tôi đã cập nhật câu trả lời của mình trong thời gian chờ đợi và cũng bao gồm các mục đích khác của .pipe(). Nếu lệnh gọi lại trả về một đối tượng bị trì hoãn, các lệnh gọi lại hoàn thành hoặc thất bại tiếp theo sẽ được đăng ký cho đối tượng đó. Tôi sẽ bao gồm một ví dụ khác. chỉnh sửa: liên quan đến nhận xét thứ hai của bạn: có.
Felix Kling

Vì vậy, một sự khác biệt là dữ liệu chảy qua pipe() trong khi then()giống như một nút lá ở cuối mà bạn phải sử dụng dữ liệu của mình và nó không chảy thêm vào nữa, và mặc dù thực tế then()trả về một Deferrednút thì nó không thực sự được sử dụng / hữu ích? Nếu điều này đúng, nó có thể hữu ích để làm rõ để bao gồm một cái gì đó như /* do something with "min"/"max" */trong mỗi mệnh đề ".then ()".
hippietrail

1
Đừng lo lắng :) Tôi cũng mất một khoảng thời gian để hiểu đầy đủ cách thức hoạt động của các đối tượng hoãn lại và phương pháp của chúng. Nhưng một khi bạn hiểu nó, nó sẽ không còn khó khăn nữa. Tôi đồng ý rằng tài liệu có thể được viết theo cách dễ dàng hơn.
Felix Kling

7

Không có trường hợp nào bạn PHẢI sử dụng then()hết pipe(). Bạn luôn có thể chọn bỏ qua giá trị pipe()sẽ chuyển vào. Có thể có một chút ảnh hưởng về hiệu suất khi sử dụng pipe- nhưng điều đó không thành vấn đề.

Vì vậy, có vẻ như bạn luôn có thể sử dụng đơn giản pipe()trong cả hai trường hợp. Tuy nhiên , bằng cách sử dụng pipe(), bạn đang thông báo với những người khác đang đọc mã của bạn (bao gồm cả chính bạn, sáu tháng kể từ bây giờ) rằng có một số tầm quan trọng đối với giá trị trả về. Nếu bạn loại bỏ nó, bạn đang vi phạm cấu trúc ngữ nghĩa này.

Nó giống như có một hàm trả về một giá trị không bao giờ được sử dụng: khó hiểu.

Vì vậy, hãy sử dụng then()khi nào bạn nên, và pipe()khi nào bạn nên ...


3
Tôi đã tìm thấy một ví dụ thực tế bằng cách sử dụng cả hai trên blog của K. Scott Allen, "Thử nghiệm đang viết": Vị trí địa lý, Mã hóa địa lý và jQuery Hứa hẹn : "Sau đó, logic điều khiển đọc khá tốt:" $(function () { $.when(getPosition()) .pipe(lookupCountry) .then(displayResults); }); "Lưu ý rằng đường ống khác với sau đó bởi vì đường ống mang lại một lời hứa mới. "
hippietrail

5

Trên thực tế, sự khác biệt giữa .then().pipe()đã được coi là không cần thiết và chúng đã được làm cho giống với jQuery phiên bản 1.8.

Từ nhận xét củajaubourg trong phiếu theo dõi lỗi # 11010 của jQuery "MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A":

Trong 1.8, chúng tôi sẽ loại bỏ đường ống cũ và thay thế nó bằng đường ống hiện tại. Nhưng hậu quả rất nghiêm trọng là chúng tôi sẽ phải nói với mọi người sử dụng không theo tiêu chuẩn được thực hiện, thất bại và tiến độ, bởi vì đề xuất không cung cấp đơn giản, HIỆU QUẢ, nghĩa là chỉ thêm một lệnh gọi lại.

(mỏ emphassis)


1
Tham khảo tốt nhất cho đến nay, tôi đang tìm kiếm các cách sử dụng nâng cao.
TWiStErRob
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.